commit 242a51027be483df6f02141b2dac817ee7cca1df Author: Alison Watson Date: Sun Nov 24 23:45:15 2019 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c983ef9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +anachronisms +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0a79965 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,187 @@ +cmake_minimum_required(VERSION 3.14) +cmake_policy(SET CMP0046 NEW) +cmake_policy(SET CMP0063 NEW) +cmake_policy(SET CMP0071 NEW) + +project(agw-quake C) + +if(NOT MSVC) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Werror") +endif() + +find_package(OpenGL REQUIRED) +find_package(PkgConfig REQUIRED) + +pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.0.0) +pkg_check_modules(FLAC REQUIRED IMPORTED_TARGET flac>=1.3.3) +pkg_check_modules(Vorbis REQUIRED IMPORTED_TARGET vorbis>=1.3.6 + vorbisfile>=1.3.6 + ogg>=1.3.4) +pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus>=1.3.1 + opusfile>=0.11 + ogg>=1.3.4) +pkg_check_modules(MikMod REQUIRED IMPORTED_TARGET libmikmod>=3.3.11) + +add_executable(quake WIN32 + source/anorm_dots.h + source/anorms.h + source/arch_def.h + source/bgmusic.c + source/bgmusic.h + source/bspfile.h + source/cd_sdl.c + source/cdaudio.h + source/cfgfile.c + source/cfgfile.h + source/chase.c + source/cl_demo.c + source/cl_input.c + source/cl_main.c + source/cl_parse.c + source/cl_tent.c + source/client.h + source/cmd.c + source/cmd.h + source/common.c + source/common.h + source/console.c + source/console.h + source/crc.c + source/crc.h + source/cvar.c + source/cvar.h + source/draw.h + source/filenames.h + source/gl_draw.c + source/gl_fog.c + source/gl_mesh.c + source/gl_model.c + source/gl_model.h + source/gl_refrag.c + source/gl_rlight.c + source/gl_rmain.c + source/gl_rmisc.c + source/gl_screen.c + source/gl_sky.c + source/gl_texmgr.c + source/gl_texmgr.h + source/gl_vidsdl.c + source/gl_warp.c + source/gl_warp_sin.h + source/glquake.h + source/host.c + source/host_cmd.c + source/image.c + source/image.h + source/in_sdl.c + source/input.h + source/keys.c + source/keys.h + source/lodepng.h + source/main_sdl.c + source/mathlib.c + source/mathlib.h + source/menu.c + source/menu.h + source/modelgen.h + source/net.h + source/net_bsd.c + source/net_defs.h + source/net_dgrm.c + source/net_dgrm.h + source/net_loop.c + source/net_loop.h + source/net_main.c + source/net_sys.h + source/net_udp.c + source/net_udp.h + source/pl_linux.c + source/platform.h + source/pr_cmds.c + source/pr_comp.h + source/pr_edict.c + source/pr_exec.c + source/progdefs.h + source/progs.h + source/protocol.h + source/q_ctype.h + source/q_sound.h + source/q_stdinc.h + source/qs_bmp.h + source/quakedef.h + source/r_alias.c + source/r_brush.c + source/r_part.c + source/r_sprite.c + source/r_world.c + source/render.h + source/resource.h + source/sbar.c + source/sbar.h + source/screen.h + source/server.h + source/snd_codec.c + source/snd_codec.h + source/snd_codeci.h + source/snd_dma.c + source/snd_flac.c + source/snd_flac.h + source/snd_mem.c + source/snd_mikmod.c + source/snd_mikmod.h + source/snd_mix.c + source/snd_mp3.c + source/snd_mp3.h + source/snd_mpg123.c + source/snd_opus.c + source/snd_opus.h + source/snd_sdl.c + source/snd_umx.c + source/snd_umx.h + source/snd_vorbis.c + source/snd_vorbis.h + source/snd_wave.c + source/snd_wave.h + source/snd_xmp.c + source/snd_xmp.h + source/spritegn.h + source/stb_image_write.h + source/strl_fn.h + source/strlcat.c + source/strlcpy.c + source/sv_main.c + source/sv_move.c + source/sv_phys.c + source/sv_user.c + source/sys.h + source/sys_sdl_unix.c + source/vid.h + source/view.c + source/view.h + source/wad.c + source/wad.h + source/world.c + source/world.h + source/wsaerror.h + source/zone.c + source/zone.h) + +target_compile_definitions( + quake + PUBLIC + -DDO_USERDIRS=1 + -DUSE_SDL2=1 + -DUSE_CODEC_FLAC=1 + -DUSE_CODEC_VORBIS=1 + -DUSE_CODEC_OPUS=1 + -DUSE_CODEC_MIKMOD=1 + -DUSE_CODEC_UMX=1) + +target_link_libraries(quake + m + OpenGL::GL + PkgConfig::SDL2 + PkgConfig::FLAC + PkgConfig::Vorbis + PkgConfig::Opus + PkgConfig::MikMod) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General +Public License instead of this License. diff --git a/source/anorm_dots.h b/source/anorm_dots.h new file mode 100644 index 0000000..21e6853 --- /dev/null +++ b/source/anorm_dots.h @@ -0,0 +1,580 @@ +/* + * anorm_dots.h + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// 0 + { 1.23, 1.30, 1.47, 1.35, 1.56, 1.71, 1.37, 1.38, + 1.59, 1.60, 1.79, 1.97, 1.88, 1.92, 1.79, 1.02, + 0.93, 1.07, 0.82, 0.87, 0.88, 0.94, 0.96, 1.14, + 1.11, 0.82, 0.83, 0.89, 0.89, 0.86, 0.94, 0.91, + 1.00, 1.21, 0.98, 1.48, 1.30, 1.57, 0.96, 1.07, + 1.14, 1.60, 1.61, 1.40, 1.37, 1.72, 1.78, 1.79, + 1.93, 1.99, 1.90, 1.68, 1.71, 1.86, 1.60, 1.68, + 1.78, 1.86, 1.93, 1.99, 1.97, 1.44, 1.22, 1.49, + 0.93, 0.99, 0.99, 1.23, 1.22, 1.44, 1.49, 0.89, + 0.89, 0.97, 0.91, 0.98, 1.19, 0.82, 0.76, 0.82, + 0.71, 0.72, 0.73, 0.76, 0.79, 0.86, 0.83, 0.72, + 0.76, 0.76, 0.89, 0.82, 0.89, 0.82, 0.89, 0.91, + 0.83, 0.96, 1.14, 0.97, 1.40, 1.19, 0.98, 0.94, + 1.00, 1.07, 1.37, 1.21, 1.48, 1.30, 1.57, 1.61, + 1.37, 0.86, 0.83, 0.91, 0.82, 0.82, 0.88, 0.89, + 0.96, 1.14, 0.98, 0.87, 0.93, 0.94, 1.02, 1.30, + 1.07, 1.35, 1.38, 1.11, 1.56, 1.92, 1.79, 1.79, + 1.59, 1.60, 1.72, 1.90, 1.79, 0.80, 0.85, 0.79, + 0.93, 0.80, 0.85, 0.77, 0.74, 0.72, 0.77, 0.74, + 0.72, 0.70, 0.70, 0.71, 0.76, 0.73, 0.79, 0.79, + 0.73, 0.76, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 1 + { 1.26, 1.26, 1.48, 1.23, 1.50, 1.71, 1.14, 1.19, + 1.38, 1.46, 1.64, 1.94, 1.87, 1.84, 1.71, 1.02, + 0.92, 1.00, 0.79, 0.85, 0.84, 0.91, 0.90, 0.98, + 0.99, 0.77, 0.77, 0.83, 0.82, 0.79, 0.86, 0.84, + 0.92, 0.99, 0.91, 1.24, 1.03, 1.33, 0.88, 0.94, + 0.97, 1.41, 1.39, 1.18, 1.11, 1.51, 1.61, 1.59, + 1.80, 1.91, 1.76, 1.54, 1.65, 1.76, 1.70, 1.70, + 1.85, 1.85, 1.97, 1.99, 1.93, 1.28, 1.09, 1.39, + 0.92, 0.97, 0.99, 1.18, 1.26, 1.52, 1.48, 0.83, + 0.85, 0.90, 0.88, 0.93, 1.00, 0.77, 0.73, 0.78, + 0.72, 0.71, 0.74, 0.75, 0.79, 0.86, 0.81, 0.75, + 0.81, 0.79, 0.96, 0.88, 0.94, 0.86, 0.93, 0.92, + 0.85, 1.08, 1.33, 1.05, 1.55, 1.31, 1.01, 1.05, + 1.27, 1.31, 1.60, 1.47, 1.70, 1.54, 1.76, 1.76, + 1.57, 0.93, 0.90, 0.99, 0.88, 0.88, 0.95, 0.97, + 1.11, 1.39, 1.20, 0.92, 0.97, 1.01, 1.10, 1.39, + 1.22, 1.51, 1.58, 1.32, 1.64, 1.97, 1.85, 1.91, + 1.77, 1.74, 1.88, 1.99, 1.91, 0.79, 0.86, 0.80, + 0.94, 0.84, 0.88, 0.74, 0.74, 0.71, 0.82, 0.77, + 0.76, 0.70, 0.73, 0.72, 0.73, 0.70, 0.74, 0.85, + 0.77, 0.82, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 2 + { 1.34, 1.27, 1.53, 1.17, 1.46, 1.71, 0.98, 1.05, + 1.20, 1.34, 1.48, 1.86, 1.82, 1.71, 1.62, 1.09, + 0.94, 0.99, 0.79, 0.85, 0.82, 0.90, 0.87, 0.93, + 0.96, 0.76, 0.74, 0.79, 0.76, 0.74, 0.79, 0.78, + 0.85, 0.92, 0.85, 1.00, 0.93, 1.06, 0.81, 0.86, + 0.89, 1.16, 1.12, 0.97, 0.95, 1.28, 1.38, 1.35, + 1.60, 1.77, 1.57, 1.33, 1.50, 1.58, 1.69, 1.63, + 1.82, 1.74, 1.91, 1.92, 1.80, 1.04, 0.97, 1.21, + 0.90, 0.93, 0.97, 1.05, 1.21, 1.48, 1.37, 0.77, + 0.80, 0.84, 0.85, 0.88, 0.92, 0.73, 0.71, 0.74, + 0.74, 0.71, 0.75, 0.73, 0.79, 0.84, 0.78, 0.79, + 0.86, 0.81, 1.05, 0.94, 0.99, 0.90, 0.95, 0.92, + 0.86, 1.24, 1.44, 1.14, 1.59, 1.34, 1.02, 1.27, + 1.50, 1.49, 1.80, 1.69, 1.86, 1.72, 1.87, 1.80, + 1.69, 1.00, 0.98, 1.23, 0.95, 0.96, 1.09, 1.16, + 1.37, 1.63, 1.46, 0.99, 1.10, 1.25, 1.24, 1.51, + 1.41, 1.67, 1.77, 1.55, 1.72, 1.95, 1.89, 1.98, + 1.91, 1.86, 1.97, 1.99, 1.94, 0.81, 0.89, 0.85, + 0.98, 0.90, 0.94, 0.75, 0.78, 0.73, 0.89, 0.83, + 0.82, 0.72, 0.77, 0.76, 0.72, 0.70, 0.71, 0.91, + 0.83, 0.89, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 3 + { 1.46, 1.34, 1.60, 1.16, 1.46, 1.71, 0.94, 0.99, + 1.05, 1.26, 1.33, 1.74, 1.76, 1.57, 1.54, 1.23, + 0.98, 1.05, 0.83, 0.89, 0.84, 0.92, 0.87, 0.91, + 0.96, 0.78, 0.74, 0.79, 0.72, 0.72, 0.75, 0.76, + 0.80, 0.88, 0.83, 0.94, 0.87, 0.95, 0.76, 0.80, + 0.82, 0.97, 0.96, 0.89, 0.88, 1.08, 1.11, 1.10, + 1.37, 1.59, 1.37, 1.07, 1.27, 1.34, 1.57, 1.45, + 1.69, 1.55, 1.77, 1.79, 1.60, 0.93, 0.90, 0.99, + 0.86, 0.87, 0.93, 0.96, 1.07, 1.35, 1.18, 0.73, + 0.76, 0.77, 0.81, 0.82, 0.85, 0.70, 0.71, 0.72, + 0.78, 0.73, 0.77, 0.73, 0.79, 0.82, 0.76, 0.83, + 0.90, 0.84, 1.18, 0.98, 1.03, 0.92, 0.95, 0.90, + 0.86, 1.32, 1.45, 1.15, 1.53, 1.27, 0.99, 1.42, + 1.65, 1.58, 1.93, 1.83, 1.94, 1.81, 1.88, 1.74, + 1.70, 1.19, 1.17, 1.44, 1.11, 1.15, 1.36, 1.41, + 1.61, 1.81, 1.67, 1.22, 1.34, 1.50, 1.42, 1.65, + 1.61, 1.82, 1.91, 1.75, 1.80, 1.89, 1.89, 1.98, + 1.99, 1.94, 1.98, 1.92, 1.87, 0.86, 0.95, 0.92, + 1.14, 0.98, 1.03, 0.79, 0.84, 0.77, 0.97, 0.90, + 0.89, 0.76, 0.82, 0.82, 0.74, 0.72, 0.71, 0.98, + 0.89, 0.97, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 4 + { 1.60, 1.44, 1.68, 1.22, 1.49, 1.71, 0.93, 0.99, + 0.99, 1.23, 1.22, 1.60, 1.68, 1.44, 1.49, 1.40, + 1.14, 1.19, 0.89, 0.96, 0.89, 0.97, 0.89, 0.91, + 0.98, 0.82, 0.76, 0.82, 0.71, 0.72, 0.73, 0.76, + 0.79, 0.86, 0.83, 0.91, 0.83, 0.89, 0.72, 0.76, + 0.76, 0.89, 0.89, 0.82, 0.82, 0.98, 0.96, 0.97, + 1.14, 1.40, 1.19, 0.94, 1.00, 1.07, 1.37, 1.21, + 1.48, 1.30, 1.57, 1.61, 1.37, 0.86, 0.83, 0.91, + 0.82, 0.82, 0.88, 0.89, 0.96, 1.14, 0.98, 0.70, + 0.72, 0.73, 0.77, 0.76, 0.79, 0.70, 0.72, 0.71, + 0.82, 0.77, 0.80, 0.74, 0.79, 0.80, 0.74, 0.87, + 0.93, 0.85, 1.23, 1.02, 1.02, 0.93, 0.93, 0.87, + 0.85, 1.30, 1.35, 1.07, 1.38, 1.11, 0.94, 1.47, + 1.71, 1.56, 1.97, 1.88, 1.92, 1.79, 1.79, 1.59, + 1.60, 1.30, 1.35, 1.56, 1.37, 1.38, 1.59, 1.60, + 1.79, 1.92, 1.79, 1.48, 1.57, 1.72, 1.61, 1.78, + 1.79, 1.93, 1.99, 1.90, 1.86, 1.78, 1.86, 1.93, + 1.99, 1.97, 1.90, 1.79, 1.72, 0.94, 1.07, 1.00, + 1.37, 1.21, 1.30, 0.86, 0.91, 0.83, 1.14, 0.98, + 0.96, 0.82, 0.88, 0.89, 0.79, 0.76, 0.73, 1.07, + 0.94, 1.11, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 5 + { 1.74, 1.57, 1.76, 1.33, 1.54, 1.71, 0.94, 1.05, + 0.99, 1.26, 1.16, 1.46, 1.60, 1.34, 1.46, 1.59, + 1.37, 1.37, 0.97, 1.11, 0.96, 1.10, 0.95, 0.94, + 1.08, 0.89, 0.82, 0.88, 0.72, 0.76, 0.75, 0.80, + 0.80, 0.88, 0.87, 0.91, 0.83, 0.87, 0.72, 0.76, + 0.74, 0.83, 0.84, 0.78, 0.79, 0.96, 0.89, 0.92, + 0.98, 1.23, 1.05, 0.86, 0.92, 0.95, 1.11, 0.98, + 1.22, 1.03, 1.34, 1.42, 1.14, 0.79, 0.77, 0.84, + 0.78, 0.76, 0.82, 0.82, 0.89, 0.97, 0.90, 0.70, + 0.71, 0.71, 0.73, 0.72, 0.74, 0.73, 0.76, 0.72, + 0.86, 0.81, 0.82, 0.76, 0.79, 0.77, 0.73, 0.90, + 0.95, 0.86, 1.18, 1.03, 0.98, 0.92, 0.90, 0.83, + 0.84, 1.19, 1.17, 0.98, 1.15, 0.97, 0.89, 1.42, + 1.65, 1.44, 1.93, 1.83, 1.81, 1.67, 1.61, 1.36, + 1.41, 1.32, 1.45, 1.58, 1.57, 1.53, 1.74, 1.70, + 1.88, 1.94, 1.81, 1.69, 1.77, 1.87, 1.79, 1.89, + 1.92, 1.98, 1.99, 1.98, 1.89, 1.65, 1.80, 1.82, + 1.91, 1.94, 1.75, 1.61, 1.50, 1.07, 1.34, 1.27, + 1.60, 1.45, 1.55, 0.93, 0.99, 0.90, 1.35, 1.18, + 1.07, 0.87, 0.93, 0.96, 0.85, 0.82, 0.77, 1.15, + 0.99, 1.27, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 6 + { 1.86, 1.71, 1.82, 1.48, 1.62, 1.71, 0.98, 1.20, + 1.05, 1.34, 1.17, 1.34, 1.53, 1.27, 1.46, 1.77, + 1.60, 1.57, 1.16, 1.38, 1.12, 1.35, 1.06, 1.00, + 1.28, 0.97, 0.89, 0.95, 0.76, 0.81, 0.79, 0.86, + 0.85, 0.92, 0.93, 0.93, 0.85, 0.87, 0.74, 0.78, + 0.74, 0.79, 0.82, 0.76, 0.79, 0.96, 0.85, 0.90, + 0.94, 1.09, 0.99, 0.81, 0.85, 0.89, 0.95, 0.90, + 0.99, 0.94, 1.10, 1.24, 0.98, 0.75, 0.73, 0.78, + 0.74, 0.72, 0.77, 0.76, 0.82, 0.89, 0.83, 0.73, + 0.71, 0.71, 0.71, 0.70, 0.72, 0.77, 0.80, 0.74, + 0.90, 0.85, 0.84, 0.78, 0.79, 0.75, 0.73, 0.92, + 0.95, 0.86, 1.05, 0.99, 0.94, 0.90, 0.86, 0.79, + 0.81, 1.00, 0.98, 0.91, 0.96, 0.89, 0.83, 1.27, + 1.50, 1.23, 1.80, 1.69, 1.63, 1.46, 1.37, 1.09, + 1.16, 1.24, 1.44, 1.49, 1.69, 1.59, 1.80, 1.69, + 1.87, 1.86, 1.72, 1.82, 1.91, 1.94, 1.92, 1.95, + 1.99, 1.98, 1.91, 1.97, 1.89, 1.51, 1.72, 1.67, + 1.77, 1.86, 1.55, 1.41, 1.25, 1.33, 1.58, 1.50, + 1.80, 1.63, 1.74, 1.04, 1.21, 0.97, 1.48, 1.37, + 1.21, 0.93, 0.97, 1.05, 0.92, 0.88, 0.84, 1.14, + 1.02, 1.34, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 7 + { 1.94, 1.84, 1.87, 1.64, 1.71, 1.71, 1.14, 1.38, + 1.19, 1.46, 1.23, 1.26, 1.48, 1.26, 1.50, 1.91, + 1.80, 1.76, 1.41, 1.61, 1.39, 1.59, 1.33, 1.24, + 1.51, 1.18, 0.97, 1.11, 0.82, 0.88, 0.86, 0.94, + 0.92, 0.99, 1.03, 0.98, 0.91, 0.90, 0.79, 0.84, + 0.77, 0.79, 0.84, 0.77, 0.83, 0.99, 0.85, 0.91, + 0.92, 1.02, 1.00, 0.79, 0.80, 0.86, 0.88, 0.84, + 0.92, 0.88, 0.97, 1.10, 0.94, 0.74, 0.71, 0.74, + 0.72, 0.70, 0.73, 0.72, 0.76, 0.82, 0.77, 0.77, + 0.73, 0.74, 0.71, 0.70, 0.73, 0.83, 0.85, 0.78, + 0.92, 0.88, 0.86, 0.81, 0.79, 0.74, 0.75, 0.92, + 0.93, 0.85, 0.96, 0.94, 0.88, 0.86, 0.81, 0.75, + 0.79, 0.93, 0.90, 0.85, 0.88, 0.82, 0.77, 1.05, + 1.27, 0.99, 1.60, 1.47, 1.39, 1.20, 1.11, 0.95, + 0.97, 1.08, 1.33, 1.31, 1.70, 1.55, 1.76, 1.57, + 1.76, 1.70, 1.54, 1.85, 1.97, 1.91, 1.99, 1.97, + 1.99, 1.91, 1.77, 1.88, 1.85, 1.39, 1.64, 1.51, + 1.58, 1.74, 1.32, 1.22, 1.01, 1.54, 1.76, 1.65, + 1.93, 1.70, 1.85, 1.28, 1.39, 1.09, 1.52, 1.48, + 1.26, 0.97, 0.99, 1.18, 1.00, 0.93, 0.90, 1.05, + 1.01, 1.31, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 8 + { 1.97, 1.92, 1.88, 1.79, 1.79, 1.71, 1.37, 1.59, + 1.38, 1.60, 1.35, 1.23, 1.47, 1.30, 1.56, 1.99, + 1.93, 1.90, 1.60, 1.78, 1.61, 1.79, 1.57, 1.48, + 1.72, 1.40, 1.14, 1.37, 0.89, 0.96, 0.94, 1.07, + 1.00, 1.21, 1.30, 1.14, 0.98, 0.96, 0.86, 0.91, + 0.83, 0.82, 0.88, 0.82, 0.89, 1.11, 0.87, 0.94, + 0.93, 1.02, 1.07, 0.80, 0.79, 0.85, 0.82, 0.80, + 0.87, 0.85, 0.93, 1.02, 0.93, 0.77, 0.72, 0.74, + 0.71, 0.70, 0.70, 0.71, 0.72, 0.77, 0.74, 0.82, + 0.76, 0.79, 0.72, 0.73, 0.76, 0.89, 0.89, 0.82, + 0.93, 0.91, 0.86, 0.83, 0.79, 0.73, 0.76, 0.91, + 0.89, 0.83, 0.89, 0.89, 0.82, 0.82, 0.76, 0.72, + 0.76, 0.86, 0.83, 0.79, 0.82, 0.76, 0.73, 0.94, + 1.00, 0.91, 1.37, 1.21, 1.14, 0.98, 0.96, 0.88, + 0.89, 0.96, 1.14, 1.07, 1.60, 1.40, 1.61, 1.37, + 1.57, 1.48, 1.30, 1.78, 1.93, 1.79, 1.99, 1.92, + 1.90, 1.79, 1.59, 1.72, 1.79, 1.30, 1.56, 1.35, + 1.38, 1.60, 1.11, 1.07, 0.94, 1.68, 1.86, 1.71, + 1.97, 1.68, 1.86, 1.44, 1.49, 1.22, 1.44, 1.49, + 1.22, 0.99, 0.99, 1.23, 1.19, 0.98, 0.97, 0.97, + 0.98, 1.19, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 9 + { 1.94, 1.97, 1.87, 1.91, 1.85, 1.71, 1.60, 1.77, + 1.58, 1.74, 1.51, 1.26, 1.48, 1.39, 1.64, 1.99, + 1.97, 1.99, 1.70, 1.85, 1.76, 1.91, 1.76, 1.70, + 1.88, 1.55, 1.33, 1.57, 0.96, 1.08, 1.05, 1.31, + 1.27, 1.47, 1.54, 1.39, 1.20, 1.11, 0.93, 0.99, + 0.90, 0.88, 0.95, 0.88, 0.97, 1.32, 0.92, 1.01, + 0.97, 1.10, 1.22, 0.84, 0.80, 0.88, 0.79, 0.79, + 0.85, 0.86, 0.92, 1.02, 0.94, 0.82, 0.76, 0.77, + 0.72, 0.73, 0.70, 0.72, 0.71, 0.74, 0.74, 0.88, + 0.81, 0.85, 0.75, 0.77, 0.82, 0.94, 0.93, 0.86, + 0.92, 0.92, 0.86, 0.85, 0.79, 0.74, 0.79, 0.88, + 0.85, 0.81, 0.82, 0.83, 0.77, 0.78, 0.73, 0.71, + 0.75, 0.79, 0.77, 0.74, 0.77, 0.73, 0.70, 0.86, + 0.92, 0.84, 1.14, 0.99, 0.98, 0.91, 0.90, 0.84, + 0.83, 0.88, 0.97, 0.94, 1.41, 1.18, 1.39, 1.11, + 1.33, 1.24, 1.03, 1.61, 1.80, 1.59, 1.91, 1.84, + 1.76, 1.64, 1.38, 1.51, 1.71, 1.26, 1.50, 1.23, + 1.19, 1.46, 0.99, 1.00, 0.91, 1.70, 1.85, 1.65, + 1.93, 1.54, 1.76, 1.52, 1.48, 1.26, 1.28, 1.39, + 1.09, 0.99, 0.97, 1.18, 1.31, 1.01, 1.05, 0.90, + 0.93, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 10 + { 1.86, 1.95, 1.82, 1.98, 1.89, 1.71, 1.80, 1.91, + 1.77, 1.86, 1.67, 1.34, 1.53, 1.51, 1.72, 1.92, + 1.91, 1.99, 1.69, 1.82, 1.80, 1.94, 1.87, 1.86, + 1.97, 1.59, 1.44, 1.69, 1.05, 1.24, 1.27, 1.49, + 1.50, 1.69, 1.72, 1.63, 1.46, 1.37, 1.00, 1.23, + 0.98, 0.95, 1.09, 0.96, 1.16, 1.55, 0.99, 1.25, + 1.10, 1.24, 1.41, 0.90, 0.85, 0.94, 0.79, 0.81, + 0.85, 0.89, 0.94, 1.09, 0.98, 0.89, 0.82, 0.83, + 0.74, 0.77, 0.72, 0.76, 0.73, 0.75, 0.78, 0.94, + 0.86, 0.91, 0.79, 0.83, 0.89, 0.99, 0.95, 0.90, + 0.90, 0.92, 0.84, 0.86, 0.79, 0.75, 0.81, 0.85, + 0.80, 0.78, 0.76, 0.77, 0.73, 0.74, 0.71, 0.71, + 0.73, 0.74, 0.74, 0.71, 0.76, 0.72, 0.70, 0.79, + 0.85, 0.78, 0.98, 0.92, 0.93, 0.85, 0.87, 0.82, + 0.79, 0.81, 0.89, 0.86, 1.16, 0.97, 1.12, 0.95, + 1.06, 1.00, 0.93, 1.38, 1.60, 1.35, 1.77, 1.71, + 1.57, 1.48, 1.20, 1.28, 1.62, 1.27, 1.46, 1.17, + 1.05, 1.34, 0.96, 0.99, 0.90, 1.63, 1.74, 1.50, + 1.80, 1.33, 1.58, 1.48, 1.37, 1.21, 1.04, 1.21, + 0.97, 0.97, 0.93, 1.05, 1.34, 1.02, 1.14, 0.84, + 0.88, 0.92, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 11 + { 1.74, 1.89, 1.76, 1.98, 1.89, 1.71, 1.93, 1.99, + 1.91, 1.94, 1.82, 1.46, 1.60, 1.65, 1.80, 1.79, + 1.77, 1.92, 1.57, 1.69, 1.74, 1.87, 1.88, 1.94, + 1.98, 1.53, 1.45, 1.70, 1.18, 1.32, 1.42, 1.58, + 1.65, 1.83, 1.81, 1.81, 1.67, 1.61, 1.19, 1.44, + 1.17, 1.11, 1.36, 1.15, 1.41, 1.75, 1.22, 1.50, + 1.34, 1.42, 1.61, 0.98, 0.92, 1.03, 0.83, 0.86, + 0.89, 0.95, 0.98, 1.23, 1.14, 0.97, 0.89, 0.90, + 0.78, 0.82, 0.76, 0.82, 0.77, 0.79, 0.84, 0.98, + 0.90, 0.98, 0.83, 0.89, 0.97, 1.03, 0.95, 0.92, + 0.86, 0.90, 0.82, 0.86, 0.79, 0.77, 0.84, 0.81, + 0.76, 0.76, 0.72, 0.73, 0.70, 0.72, 0.71, 0.73, + 0.73, 0.72, 0.74, 0.71, 0.78, 0.74, 0.72, 0.75, + 0.80, 0.76, 0.94, 0.88, 0.91, 0.83, 0.87, 0.84, + 0.79, 0.76, 0.82, 0.80, 0.97, 0.89, 0.96, 0.88, + 0.95, 0.94, 0.87, 1.11, 1.37, 1.10, 1.59, 1.57, + 1.37, 1.33, 1.05, 1.08, 1.54, 1.34, 1.46, 1.16, + 0.99, 1.26, 0.96, 1.05, 0.92, 1.45, 1.55, 1.27, + 1.60, 1.07, 1.34, 1.35, 1.18, 1.07, 0.93, 0.99, + 0.90, 0.93, 0.87, 0.96, 1.27, 0.99, 1.15, 0.77, + 0.82, 0.85, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 12 + { 1.60, 1.78, 1.68, 1.93, 1.86, 1.71, 1.97, 1.99, + 1.99, 1.97, 1.93, 1.60, 1.68, 1.78, 1.86, 1.61, + 1.57, 1.79, 1.37, 1.48, 1.59, 1.72, 1.79, 1.92, + 1.90, 1.38, 1.35, 1.60, 1.23, 1.30, 1.47, 1.56, + 1.71, 1.88, 1.79, 1.92, 1.79, 1.79, 1.30, 1.56, + 1.35, 1.37, 1.59, 1.38, 1.60, 1.90, 1.48, 1.72, + 1.57, 1.61, 1.79, 1.21, 1.00, 1.30, 0.89, 0.94, + 0.96, 1.07, 1.14, 1.40, 1.37, 1.14, 0.96, 0.98, + 0.82, 0.88, 0.82, 0.89, 0.83, 0.86, 0.91, 1.02, + 0.93, 1.07, 0.87, 0.94, 1.11, 1.02, 0.93, 0.93, + 0.82, 0.87, 0.80, 0.85, 0.79, 0.80, 0.85, 0.77, + 0.72, 0.74, 0.71, 0.70, 0.70, 0.71, 0.72, 0.77, + 0.74, 0.72, 0.76, 0.73, 0.82, 0.79, 0.76, 0.73, + 0.79, 0.76, 0.93, 0.86, 0.91, 0.83, 0.89, 0.89, + 0.82, 0.72, 0.76, 0.76, 0.89, 0.82, 0.89, 0.82, + 0.89, 0.91, 0.83, 0.96, 1.14, 0.97, 1.40, 1.44, + 1.19, 1.22, 0.99, 0.98, 1.49, 1.44, 1.49, 1.22, + 0.99, 1.23, 0.98, 1.19, 0.97, 1.21, 1.30, 1.00, + 1.37, 0.94, 1.07, 1.14, 0.98, 0.96, 0.86, 0.91, + 0.83, 0.88, 0.82, 0.89, 1.11, 0.94, 1.07, 0.73, + 0.76, 0.79, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 13 + { 1.46, 1.65, 1.60, 1.82, 1.80, 1.71, 1.93, 1.91, + 1.99, 1.94, 1.98, 1.74, 1.76, 1.89, 1.89, 1.42, + 1.34, 1.61, 1.11, 1.22, 1.36, 1.50, 1.61, 1.81, + 1.75, 1.15, 1.17, 1.41, 1.18, 1.19, 1.42, 1.44, + 1.65, 1.83, 1.67, 1.94, 1.81, 1.88, 1.32, 1.58, + 1.45, 1.57, 1.74, 1.53, 1.70, 1.98, 1.69, 1.87, + 1.77, 1.79, 1.92, 1.45, 1.27, 1.55, 0.97, 1.07, + 1.11, 1.34, 1.37, 1.59, 1.60, 1.35, 1.07, 1.18, + 0.86, 0.93, 0.87, 0.96, 0.90, 0.93, 0.99, 1.03, + 0.95, 1.15, 0.90, 0.99, 1.27, 0.98, 0.90, 0.92, + 0.78, 0.83, 0.77, 0.84, 0.79, 0.82, 0.86, 0.73, + 0.71, 0.73, 0.72, 0.70, 0.73, 0.72, 0.76, 0.81, + 0.76, 0.76, 0.82, 0.77, 0.89, 0.85, 0.82, 0.75, + 0.80, 0.80, 0.94, 0.88, 0.94, 0.87, 0.95, 0.96, + 0.88, 0.72, 0.74, 0.76, 0.83, 0.78, 0.84, 0.79, + 0.87, 0.91, 0.83, 0.89, 0.98, 0.92, 1.23, 1.34, + 1.05, 1.16, 0.99, 0.96, 1.46, 1.57, 1.54, 1.33, + 1.05, 1.26, 1.08, 1.37, 1.10, 0.98, 1.03, 0.92, + 1.14, 0.86, 0.95, 0.97, 0.90, 0.89, 0.79, 0.84, + 0.77, 0.82, 0.76, 0.82, 0.97, 0.89, 0.98, 0.71, + 0.72, 0.74, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 14 + { 1.34, 1.51, 1.53, 1.67, 1.72, 1.71, 1.80, 1.77, + 1.91, 1.86, 1.98, 1.86, 1.82, 1.95, 1.89, 1.24, + 1.10, 1.41, 0.95, 0.99, 1.09, 1.25, 1.37, 1.63, + 1.55, 0.96, 0.98, 1.16, 1.05, 1.00, 1.27, 1.23, + 1.50, 1.69, 1.46, 1.86, 1.72, 1.87, 1.24, 1.49, + 1.44, 1.69, 1.80, 1.59, 1.69, 1.97, 1.82, 1.94, + 1.91, 1.92, 1.99, 1.63, 1.50, 1.74, 1.16, 1.33, + 1.38, 1.58, 1.60, 1.77, 1.80, 1.48, 1.21, 1.37, + 0.90, 0.97, 0.93, 1.05, 0.97, 1.04, 1.21, 0.99, + 0.95, 1.14, 0.92, 1.02, 1.34, 0.94, 0.86, 0.90, + 0.74, 0.79, 0.75, 0.81, 0.79, 0.84, 0.86, 0.71, + 0.71, 0.73, 0.76, 0.73, 0.77, 0.74, 0.80, 0.85, + 0.78, 0.81, 0.89, 0.84, 0.97, 0.92, 0.88, 0.79, + 0.85, 0.86, 0.98, 0.92, 1.00, 0.93, 1.06, 1.12, + 0.95, 0.74, 0.74, 0.78, 0.79, 0.76, 0.82, 0.79, + 0.87, 0.93, 0.85, 0.85, 0.94, 0.90, 1.09, 1.27, + 0.99, 1.17, 1.05, 0.96, 1.46, 1.71, 1.62, 1.48, + 1.20, 1.34, 1.28, 1.57, 1.35, 0.90, 0.94, 0.85, + 0.98, 0.81, 0.89, 0.89, 0.83, 0.82, 0.75, 0.78, + 0.73, 0.77, 0.72, 0.76, 0.89, 0.83, 0.91, 0.71, + 0.70, 0.72, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 15 + { 1.26, 1.39, 1.48, 1.51, 1.64, 1.71, 1.60, 1.58, + 1.77, 1.74, 1.91, 1.94, 1.87, 1.97, 1.85, 1.10, + 0.97, 1.22, 0.88, 0.92, 0.95, 1.01, 1.11, 1.39, + 1.32, 0.88, 0.90, 0.97, 0.96, 0.93, 1.05, 0.99, + 1.27, 1.47, 1.20, 1.70, 1.54, 1.76, 1.08, 1.31, + 1.33, 1.70, 1.76, 1.55, 1.57, 1.88, 1.85, 1.91, + 1.97, 1.99, 1.99, 1.70, 1.65, 1.85, 1.41, 1.54, + 1.61, 1.76, 1.80, 1.91, 1.93, 1.52, 1.26, 1.48, + 0.92, 0.99, 0.97, 1.18, 1.09, 1.28, 1.39, 0.94, + 0.93, 1.05, 0.92, 1.01, 1.31, 0.88, 0.81, 0.86, + 0.72, 0.75, 0.74, 0.79, 0.79, 0.86, 0.85, 0.71, + 0.73, 0.75, 0.82, 0.77, 0.83, 0.78, 0.85, 0.88, + 0.81, 0.88, 0.97, 0.90, 1.18, 1.00, 0.93, 0.86, + 0.92, 0.94, 1.14, 0.99, 1.24, 1.03, 1.33, 1.39, + 1.11, 0.79, 0.77, 0.84, 0.79, 0.77, 0.84, 0.83, + 0.90, 0.98, 0.91, 0.85, 0.92, 0.91, 1.02, 1.26, + 1.00, 1.23, 1.19, 0.99, 1.50, 1.84, 1.71, 1.64, + 1.38, 1.46, 1.51, 1.76, 1.59, 0.84, 0.88, 0.80, + 0.94, 0.79, 0.86, 0.82, 0.77, 0.76, 0.74, 0.74, + 0.71, 0.73, 0.70, 0.72, 0.82, 0.77, 0.85, 0.74, + 0.70, 0.73, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + } + diff --git a/source/anorms.h b/source/anorms.h new file mode 100644 index 0000000..35a903e --- /dev/null +++ b/source/anorms.h @@ -0,0 +1,182 @@ +/* + * anorms.h + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + { -0.525731, 0.000000, 0.850651 }, + { -0.442863, 0.238856, 0.864188 }, + { -0.295242, 0.000000, 0.955423 }, + { -0.309017, 0.500000, 0.809017 }, + { -0.162460, 0.262866, 0.951056 }, + { 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.850651, 0.525731 }, + { -0.147621, 0.716567, 0.681718 }, + { 0.147621, 0.716567, 0.681718 }, + { 0.000000, 0.525731, 0.850651 }, + { 0.309017, 0.500000, 0.809017 }, + { 0.525731, 0.000000, 0.850651 }, + { 0.295242, 0.000000, 0.955423 }, + { 0.442863, 0.238856, 0.864188 }, + { 0.162460, 0.262866, 0.951056 }, + { -0.681718, 0.147621, 0.716567 }, + { -0.809017, 0.309017, 0.500000 }, + { -0.587785, 0.425325, 0.688191 }, + { -0.850651, 0.525731, 0.000000 }, + { -0.864188, 0.442863, 0.238856 }, + { -0.716567, 0.681718, 0.147621 }, + { -0.688191, 0.587785, 0.425325 }, + { -0.500000, 0.809017, 0.309017 }, + { -0.238856, 0.864188, 0.442863 }, + { -0.425325, 0.688191, 0.587785 }, + { -0.716567, 0.681718, -0.147621 }, + { -0.500000, 0.809017, -0.309017 }, + { -0.525731, 0.850651, 0.000000 }, + { 0.000000, 0.850651, -0.525731 }, + { -0.238856, 0.864188, -0.442863 }, + { 0.000000, 0.955423, -0.295242 }, + { -0.262866, 0.951056, -0.162460 }, + { 0.000000, 1.000000, 0.000000 }, + { 0.000000, 0.955423, 0.295242 }, + { -0.262866, 0.951056, 0.162460 }, + { 0.238856, 0.864188, 0.442863 }, + { 0.262866, 0.951056, 0.162460 }, + { 0.500000, 0.809017, 0.309017 }, + { 0.238856, 0.864188, -0.442863 }, + { 0.262866, 0.951056, -0.162460 }, + { 0.500000, 0.809017, -0.309017 }, + { 0.850651, 0.525731, 0.000000 }, + { 0.716567, 0.681718, 0.147621 }, + { 0.716567, 0.681718, -0.147621 }, + { 0.525731, 0.850651, 0.000000 }, + { 0.425325, 0.688191, 0.587785 }, + { 0.864188, 0.442863, 0.238856 }, + { 0.688191, 0.587785, 0.425325 }, + { 0.809017, 0.309017, 0.500000 }, + { 0.681718, 0.147621, 0.716567 }, + { 0.587785, 0.425325, 0.688191 }, + { 0.955423, 0.295242, 0.000000 }, + { 1.000000, 0.000000, 0.000000 }, + { 0.951056, 0.162460, 0.262866 }, + { 0.850651, -0.525731, 0.000000 }, + { 0.955423, -0.295242, 0.000000 }, + { 0.864188, -0.442863, 0.238856 }, + { 0.951056, -0.162460, 0.262866 }, + { 0.809017, -0.309017, 0.500000 }, + { 0.681718, -0.147621, 0.716567 }, + { 0.850651, 0.000000, 0.525731 }, + { 0.864188, 0.442863, -0.238856 }, + { 0.809017, 0.309017, -0.500000 }, + { 0.951056, 0.162460, -0.262866 }, + { 0.525731, 0.000000, -0.850651 }, + { 0.681718, 0.147621, -0.716567 }, + { 0.681718, -0.147621, -0.716567 }, + { 0.850651, 0.000000, -0.525731 }, + { 0.809017, -0.309017, -0.500000 }, + { 0.864188, -0.442863, -0.238856 }, + { 0.951056, -0.162460, -0.262866 }, + { 0.147621, 0.716567, -0.681718 }, + { 0.309017, 0.500000, -0.809017 }, + { 0.425325, 0.688191, -0.587785 }, + { 0.442863, 0.238856, -0.864188 }, + { 0.587785, 0.425325, -0.688191 }, + { 0.688191, 0.587785, -0.425325 }, + { -0.147621, 0.716567, -0.681718 }, + { -0.309017, 0.500000, -0.809017 }, + { 0.000000, 0.525731, -0.850651 }, + { -0.525731, 0.000000, -0.850651 }, + { -0.442863, 0.238856, -0.864188 }, + { -0.295242, 0.000000, -0.955423 }, + { -0.162460, 0.262866, -0.951056 }, + { 0.000000, 0.000000, -1.000000 }, + { 0.295242, 0.000000, -0.955423 }, + { 0.162460, 0.262866, -0.951056 }, + { -0.442863, -0.238856, -0.864188 }, + { -0.309017, -0.500000, -0.809017 }, + { -0.162460, -0.262866, -0.951056 }, + { 0.000000, -0.850651, -0.525731 }, + { -0.147621, -0.716567, -0.681718 }, + { 0.147621, -0.716567, -0.681718 }, + { 0.000000, -0.525731, -0.850651 }, + { 0.309017, -0.500000, -0.809017 }, + { 0.442863, -0.238856, -0.864188 }, + { 0.162460, -0.262866, -0.951056 }, + { 0.238856, -0.864188, -0.442863 }, + { 0.500000, -0.809017, -0.309017 }, + { 0.425325, -0.688191, -0.587785 }, + { 0.716567, -0.681718, -0.147621 }, + { 0.688191, -0.587785, -0.425325 }, + { 0.587785, -0.425325, -0.688191 }, + { 0.000000, -0.955423, -0.295242 }, + { 0.000000, -1.000000, 0.000000 }, + { 0.262866, -0.951056, -0.162460 }, + { 0.000000, -0.850651, 0.525731 }, + { 0.000000, -0.955423, 0.295242 }, + { 0.238856, -0.864188, 0.442863 }, + { 0.262866, -0.951056, 0.162460 }, + { 0.500000, -0.809017, 0.309017 }, + { 0.716567, -0.681718, 0.147621 }, + { 0.525731, -0.850651, 0.000000 }, + { -0.238856, -0.864188, -0.442863 }, + { -0.500000, -0.809017, -0.309017 }, + { -0.262866, -0.951056, -0.162460 }, + { -0.850651, -0.525731, 0.000000 }, + { -0.716567, -0.681718, -0.147621 }, + { -0.716567, -0.681718, 0.147621 }, + { -0.525731, -0.850651, 0.000000 }, + { -0.500000, -0.809017, 0.309017 }, + { -0.238856, -0.864188, 0.442863 }, + { -0.262866, -0.951056, 0.162460 }, + { -0.864188, -0.442863, 0.238856 }, + { -0.809017, -0.309017, 0.500000 }, + { -0.688191, -0.587785, 0.425325 }, + { -0.681718, -0.147621, 0.716567 }, + { -0.442863, -0.238856, 0.864188 }, + { -0.587785, -0.425325, 0.688191 }, + { -0.309017, -0.500000, 0.809017 }, + { -0.147621, -0.716567, 0.681718 }, + { -0.425325, -0.688191, 0.587785 }, + { -0.162460, -0.262866, 0.951056 }, + { 0.442863, -0.238856, 0.864188 }, + { 0.162460, -0.262866, 0.951056 }, + { 0.309017, -0.500000, 0.809017 }, + { 0.147621, -0.716567, 0.681718 }, + { 0.000000, -0.525731, 0.850651 }, + { 0.425325, -0.688191, 0.587785 }, + { 0.587785, -0.425325, 0.688191 }, + { 0.688191, -0.587785, 0.425325 }, + { -0.955423, 0.295242, 0.000000 }, + { -0.951056, 0.162460, 0.262866 }, + { -1.000000, 0.000000, 0.000000 }, + { -0.850651, 0.000000, 0.525731 }, + { -0.955423, -0.295242, 0.000000 }, + { -0.951056, -0.162460, 0.262866 }, + { -0.864188, 0.442863, -0.238856 }, + { -0.951056, 0.162460, -0.262866 }, + { -0.809017, 0.309017, -0.500000 }, + { -0.864188, -0.442863, -0.238856 }, + { -0.951056, -0.162460, -0.262866 }, + { -0.809017, -0.309017, -0.500000 }, + { -0.681718, 0.147621, -0.716567 }, + { -0.681718, -0.147621, -0.716567 }, + { -0.850651, 0.000000, -0.525731 }, + { -0.688191, 0.587785, -0.425325 }, + { -0.587785, 0.425325, -0.688191 }, + { -0.425325, 0.688191, -0.587785 }, + { -0.425325, -0.688191, -0.587785 }, + { -0.587785, -0.425325, -0.688191 }, + { -0.688191, -0.587785, -0.425325 }, diff --git a/source/arch_def.h b/source/arch_def.h new file mode 100644 index 0000000..4a27d9a --- /dev/null +++ b/source/arch_def.h @@ -0,0 +1,170 @@ +/* + * arch_def.h + * platform specific definitions + * - standalone header + * - doesn't and must not include any other headers + * - shouldn't depend on compiler.h, q_stdinc.h, or + * any other headers + * + * Copyright (C) 2007-2016 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ARCHDEFS_H +#define ARCHDEFS_H + + +#if defined(__DJGPP__) || defined(__MSDOS__) || defined(__DOS__) || defined(_MSDOS) + +# if !defined(PLATFORM_DOS) +# define PLATFORM_DOS 1 +# endif + +#elif defined(__OS2__) || defined(__EMX__) + +# if !defined(PLATFORM_OS2) +# define PLATFORM_OS2 1 +# endif + +#elif defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(__NT__) || defined(_Windows) + +# if !defined(PLATFORM_WINDOWS) +# define PLATFORM_WINDOWS 1 +# endif + +#elif defined(__APPLE__) && defined(__MACH__) /* Mac OS X */ + +# if !defined(PLATFORM_OSX) +# define PLATFORM_OSX 1 +# endif + +#elif defined(macintosh) /* Mac OS classic */ + +# if !defined(PLATFORM_MAC) +# define PLATFORM_MAC 1 +# endif + +#elif defined(__MORPHOS__) || defined(__AROS__) || defined(AMIGAOS) || \ + defined(__amigaos__) || defined(__amigaos4__) ||defined(__amigados__) || \ + defined(AMIGA) || defined(_AMIGA) || defined(__AMIGA__) + +# if !defined(PLATFORM_AMIGA) +# define PLATFORM_AMIGA 1 +# endif + +#elif defined(__riscos__) + +# if !defined(PLATFORM_RISCOS) +# define PLATFORM_RISCOS 1 +# endif + +#else /* here goes the unix platforms */ + +#if defined(__unix) || defined(__unix__) || defined(unix) || \ + defined(__linux__) || defined(__linux) || \ + defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) /* Debian GNU/kFreeBSD */ || \ + defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__hpux) || defined(__hpux__) || defined(_hpux) || \ + defined(__sun) || defined(sun) || \ + defined(__sgi) || defined(sgi) || defined(__sgi__) || \ + defined(__GNU__) /* GNU/Hurd */ || \ + defined(__QNX__) || defined(__QNXNTO__) +# if !defined(PLATFORM_UNIX) +# define PLATFORM_UNIX 1 +# endif +#endif + +#endif /* PLATFORM_xxx */ + + +#if defined (PLATFORM_OSX) /* OS X is unix-based */ +# if !defined(PLATFORM_UNIX) +# define PLATFORM_UNIX 2 +# endif +#endif /* OS X -> PLATFORM_UNIX */ + + +#if defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) /* Debian GNU/kFreeBSD */ || \ + defined(__OpenBSD__) || defined(__NetBSD__) +# if !defined(PLATFORM_BSD) +# define PLATFORM_BSD 1 +# endif +#endif /* PLATFORM_BSD (for convenience) */ + + +#if defined(PLATFORM_AMIGA) && !defined(PLATFORM_AMIGAOS3) +# if !defined(__MORPHOS__) && !defined(__AROS__) && !defined(__amigaos4__) +# define PLATFORM_AMIGAOS3 1 +# endif +#endif /* PLATFORM_AMIGAOS3 (for convenience) */ + + +#if defined(_WIN64) +# define PLATFORM_STRING "Win64" +#elif defined(PLATFORM_WINDOWS) +# define PLATFORM_STRING "Windows" +#elif defined(PLATFORM_DOS) +# define PLATFORM_STRING "DOS" +#elif defined(PLATFORM_OS2) +# define PLATFORM_STRING "OS/2" +#elif defined(__linux__) || defined(__linux) +# define PLATFORM_STRING "Linux" +#elif defined(__DragonFly__) +# define PLATFORM_STRING "DragonFly" +#elif defined(__FreeBSD__) +# define PLATFORM_STRING "FreeBSD" +#elif defined(__NetBSD__) +# define PLATFORM_STRING "NetBSD" +#elif defined(__OpenBSD__) +# define PLATFORM_STRING "OpenBSD" +#elif defined(__MORPHOS__) +# define PLATFORM_STRING "MorphOS" +#elif defined(__AROS__) +# define PLATFORM_STRING "AROS" +#elif defined(__amigaos4__) +# define PLATFORM_STRING "AmigaOS4" +#elif defined(PLATFORM_AMIGA) +# define PLATFORM_STRING "AmigaOS" +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_STRING "QNX" +#elif defined(PLATFORM_OSX) +# define PLATFORM_STRING "MacOSX" +#elif defined(PLATFORM_MAC) +# define PLATFORM_STRING "MacOS" +#elif defined(__hpux) || defined(__hpux__) || defined(_hpux) +# define PLATFORM_STRING "HP-UX" +#elif (defined(__sun) || defined(sun)) && (defined(__svr4__) || defined(__SVR4)) +# define PLATFORM_STRING "Solaris" +#elif defined(__sun) || defined(sun) +# define PLATFORM_STRING "SunOS" +#elif defined(__sgi) || defined(sgi) || defined(__sgi__) +# define PLATFORM_STRING "Irix" +#elif defined(PLATFORM_RISCOS) +# define PLATFORM_STRING "RiscOS" +#elif defined(__GNU__) +# define PLATFORM_STRING "GNU/Hurd" +#elif defined(PLATFORM_UNIX) +# define PLATFORM_STRING "Unix" +#else +# define PLATFORM_STRING "Unknown" +# warning "Platform is UNKNOWN." +#endif /* PLATFORM_STRING */ + +#endif /* ARCHDEFS_H */ + diff --git a/source/bgmusic.c b/source/bgmusic.c new file mode 100644 index 0000000..65df3ca --- /dev/null +++ b/source/bgmusic.c @@ -0,0 +1,469 @@ +/* + * Background music handling for Quakespasm (adapted from uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010-2018 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" +#include "snd_codec.h" +#include "bgmusic.h" + +#define MUSIC_DIRNAME "music" + +qboolean bgmloop; +cvar_t bgm_extmusic = {"bgm_extmusic", "1", CVAR_ARCHIVE}; + +static qboolean no_extmusic= false; +static float old_volume = -1.0f; + +typedef enum _bgm_player +{ + BGM_NONE = -1, + BGM_MIDIDRV = 1, + BGM_STREAMER +} bgm_player_t; + +typedef struct music_handler_s +{ + unsigned int type; /* 1U << n (see snd_codec.h) */ + bgm_player_t player; /* Enumerated bgm player type */ + int is_available; /* -1 means not present */ + const char *ext; /* Expected file extension */ + const char *dir; /* Where to look for music file */ + struct music_handler_s *next; +} music_handler_t; + +static music_handler_t wanted_handlers[] = +{ + { CODECTYPE_VORBIS,BGM_STREAMER,-1, "ogg", MUSIC_DIRNAME, NULL }, + { CODECTYPE_OPUS, BGM_STREAMER, -1, "opus", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MP3, BGM_STREAMER, -1, "mp3", MUSIC_DIRNAME, NULL }, + { CODECTYPE_FLAC, BGM_STREAMER, -1, "flac", MUSIC_DIRNAME, NULL }, + { CODECTYPE_WAV, BGM_STREAMER, -1, "wav", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "it", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "s3m", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "xm", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "mod", MUSIC_DIRNAME, NULL }, + { CODECTYPE_UMX, BGM_STREAMER, -1, "umx", MUSIC_DIRNAME, NULL }, + { CODECTYPE_NONE, BGM_NONE, -1, NULL, NULL, NULL } +}; + +static music_handler_t *music_handlers = NULL; + +#define ANY_CODECTYPE 0xFFFFFFFF +#define CDRIP_TYPES (CODECTYPE_VORBIS | CODECTYPE_MP3 | CODECTYPE_FLAC | CODECTYPE_WAV | CODECTYPE_OPUS) +#define CDRIPTYPE(x) (((x) & CDRIP_TYPES) != 0) + +static snd_stream_t *bgmstream = NULL; + +static void BGM_Play_f (void) +{ + if (Cmd_Argc() == 2) + { + BGM_Play (Cmd_Argv(1)); + } + else + { + Con_Printf ("music \n"); + return; + } +} + +static void BGM_Pause_f (void) +{ + BGM_Pause (); +} + +static void BGM_Resume_f (void) +{ + BGM_Resume (); +} + +static void BGM_Loop_f (void) +{ + if (Cmd_Argc() == 2) + { + if (q_strcasecmp(Cmd_Argv(1), "0") == 0 || + q_strcasecmp(Cmd_Argv(1),"off") == 0) + bgmloop = false; + else if (q_strcasecmp(Cmd_Argv(1), "1") == 0 || + q_strcasecmp(Cmd_Argv(1),"on") == 0) + bgmloop = true; + else if (q_strcasecmp(Cmd_Argv(1),"toggle") == 0) + bgmloop = !bgmloop; + } + + if (bgmloop) + Con_Printf("Music will be looped\n"); + else + Con_Printf("Music will not be looped\n"); +} + +static void BGM_Stop_f (void) +{ + BGM_Stop(); +} + +qboolean BGM_Init (void) +{ + music_handler_t *handlers = NULL; + int i; + + Cvar_RegisterVariable(&bgm_extmusic); + Cmd_AddCommand("music", BGM_Play_f); + Cmd_AddCommand("music_pause", BGM_Pause_f); + Cmd_AddCommand("music_resume", BGM_Resume_f); + Cmd_AddCommand("music_loop", BGM_Loop_f); + Cmd_AddCommand("music_stop", BGM_Stop_f); + + if (COM_CheckParm("-noextmusic") != 0) + no_extmusic = true; + + bgmloop = true; + + for (i = 0; wanted_handlers[i].type != CODECTYPE_NONE; i++) + { + switch (wanted_handlers[i].player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + wanted_handlers[i].is_available = + S_CodecIsAvailable(wanted_handlers[i].type); + break; + case BGM_NONE: + default: + break; + } + if (wanted_handlers[i].is_available != -1) + { + if (handlers) + { + handlers->next = &wanted_handlers[i]; + handlers = handlers->next; + } + else + { + music_handlers = &wanted_handlers[i]; + handlers = music_handlers; + } + } + } + + return true; +} + +void BGM_Shutdown (void) +{ + BGM_Stop(); +/* sever our connections to + * midi_drv and snd_codec */ + music_handlers = NULL; +} + +static void BGM_Play_noext (const char *filename, unsigned int allowed_types) +{ + char tmp[MAX_QPATH]; + music_handler_t *handler; + + handler = music_handlers; + while (handler) + { + if (! (handler->type & allowed_types)) + { + handler = handler->next; + continue; + } + if (!handler->is_available) + { + handler = handler->next; + continue; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s.%s", + handler->dir, filename, handler->ext); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + handler = handler->next; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_Play (const char *filename) +{ + char tmp[MAX_QPATH]; + const char *ext; + music_handler_t *handler; + + BGM_Stop(); + + if (music_handlers == NULL) + return; + + if (!filename || !*filename) + { + Con_DPrintf("null music file name\n"); + return; + } + + ext = COM_FileGetExtension(filename); + if (! *ext) /* try all things */ + { + BGM_Play_noext(filename, ANY_CODECTYPE); + return; + } + + handler = music_handlers; + while (handler) + { + if (handler->is_available && + !q_strcasecmp(ext, handler->ext)) + break; + handler = handler->next; + } + if (!handler) + { + Con_Printf("Unhandled extension for %s\n", filename); + return; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s", handler->dir, filename); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_PlayCDtrack (byte track, qboolean looping) +{ +/* instead of searching by the order of music_handlers, do so by + * the order of searchpath priority: the file from the searchpath + * with the highest path_id is most likely from our own gamedir + * itself. This way, if a mod has track02 as a *.mp3 file, which + * is below *.ogg in the music_handler order, the mp3 will still + * have priority over track02.ogg from, say, id1. + */ + char tmp[MAX_QPATH]; + const char *ext; + unsigned int path_id, prev_id, type; + music_handler_t *handler; + + BGM_Stop(); + if (CDAudio_Play(track, looping) == 0) + return; /* success */ + + if (music_handlers == NULL) + return; + + if (no_extmusic || !bgm_extmusic.value) + return; + + prev_id = 0; + type = 0; + ext = NULL; + handler = music_handlers; + while (handler) + { + if (! handler->is_available) + goto _next; + if (! CDRIPTYPE(handler->type)) + goto _next; + q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s", + MUSIC_DIRNAME, (int)track, handler->ext); + if (! COM_FileExists(tmp, &path_id)) + goto _next; + if (path_id > prev_id) + { + prev_id = path_id; + type = handler->type; + ext = handler->ext; + } + _next: + handler = handler->next; + } + if (ext == NULL) + Con_Printf("Couldn't find a cdrip for track %d\n", (int)track); + else + { + q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s", + MUSIC_DIRNAME, (int)track, ext); + bgmstream = S_CodecOpenStreamType(tmp, type); + if (! bgmstream) + Con_Printf("Couldn't handle music file %s\n", tmp); + } +} + +void BGM_Stop (void) +{ + if (bgmstream) + { + bgmstream->status = STREAM_NONE; + S_CodecCloseStream(bgmstream); + bgmstream = NULL; + s_rawend = 0; + } +} + +void BGM_Pause (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PLAY) + bgmstream->status = STREAM_PAUSE; + } +} + +void BGM_Resume (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PAUSE) + bgmstream->status = STREAM_PLAY; + } +} + +static void BGM_UpdateStream (void) +{ + qboolean did_rewind = false; + int res; /* Number of bytes read. */ + int bufferSamples; + int fileSamples; + int fileBytes; + byte raw[16384]; + + if (bgmstream->status != STREAM_PLAY) + return; + + /* don't bother playing anything if musicvolume is 0 */ + if (bgmvolume.value <= 0) + return; + + /* see how many samples should be copied into the raw buffer */ + if (s_rawend < paintedtime) + s_rawend = paintedtime; + + while (s_rawend < paintedtime + MAX_RAW_SAMPLES) + { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend - paintedtime); + + /* decide how much data needs to be read from the file */ + fileSamples = bufferSamples * bgmstream->info.rate / shm->speed; + if (!fileSamples) + return; + + /* our max buffer size */ + fileBytes = fileSamples * (bgmstream->info.width * bgmstream->info.channels); + if (fileBytes > (int) sizeof(raw)) + { + fileBytes = (int) sizeof(raw); + fileSamples = fileBytes / + (bgmstream->info.width * bgmstream->info.channels); + } + + /* Read */ + res = S_CodecReadStream(bgmstream, fileBytes, raw); + if (res < fileBytes) + { + fileBytes = res; + fileSamples = res / (bgmstream->info.width * bgmstream->info.channels); + } + + if (res > 0) /* data: add to raw buffer */ + { + S_RawSamples(fileSamples, bgmstream->info.rate, + bgmstream->info.width, + bgmstream->info.channels, + raw, bgmvolume.value); + did_rewind = false; + } + else if (res == 0) /* EOF */ + { + if (bgmloop) + { + if (did_rewind) + { + Con_Printf("Stream keeps returning EOF.\n"); + BGM_Stop(); + return; + } + + res = S_CodecRewindStream(bgmstream); + if (res != 0) + { + Con_Printf("Stream seek error (%i), stopping.\n", res); + BGM_Stop(); + return; + } + did_rewind = true; + } + else + { + BGM_Stop(); + return; + } + } + else /* res < 0: some read error */ + { + Con_Printf("Stream read error (%i), stopping.\n", res); + BGM_Stop(); + return; + } + } +} + +void BGM_Update (void) +{ + if (old_volume != bgmvolume.value) + { + if (bgmvolume.value < 0) + Cvar_SetQuick (&bgmvolume, "0"); + else if (bgmvolume.value > 1) + Cvar_SetQuick (&bgmvolume, "1"); + old_volume = bgmvolume.value; + } + if (bgmstream) + BGM_UpdateStream (); +} + diff --git a/source/bgmusic.h b/source/bgmusic.h new file mode 100644 index 0000000..ec30820 --- /dev/null +++ b/source/bgmusic.h @@ -0,0 +1,43 @@ +/* + * Background music handling for Quakespasm (adapted from uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BGMUSIC_H_ +#define _BGMUSIC_H_ + +extern qboolean bgmloop; +extern cvar_t bgm_extmusic; + +qboolean BGM_Init (void); +void BGM_Shutdown (void); + +void BGM_Play (const char *filename); +void BGM_Stop (void); +void BGM_Update (void); +void BGM_Pause (void); +void BGM_Resume (void); + +void BGM_PlayCDtrack (byte track, qboolean looping); + +#endif /* _BGMUSIC_H_ */ + diff --git a/source/bspfile.h b/source/bspfile.h new file mode 100644 index 0000000..6da832f --- /dev/null +++ b/source/bspfile.h @@ -0,0 +1,410 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __BSPFILE_H +#define __BSPFILE_H + +// upper design bounds + +#define MAX_MAP_HULLS 4 + +#define MAX_MAP_MODELS 256 +#define MAX_MAP_BRUSHES 4096 +#define MAX_MAP_ENTITIES 1024 +#define MAX_MAP_ENTSTRING 65536 + +#define MAX_MAP_PLANES 32767 +#define MAX_MAP_NODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES 32767 +//#define MAX_MAP_LEAFS 80000 //johnfitz -- was 8192 +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 +#define MAX_MAP_MARKSURFACES 65535 +#define MAX_MAP_TEXINFO 4096 +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +#define MAX_MAP_TEXTURES 512 +#define MAX_MAP_MIPTEX 0x200000 +#define MAX_MAP_LIGHTING 0x100000 +#define MAX_MAP_VISIBILITY 0x100000 + +#define MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define BSPVERSION 29 + +/* RMQ support (2PSB). 32bits instead of shorts for all but bbox sizes (which + * still use shorts) */ +#define BSP2VERSION_2PSB (('B' << 24) | ('S' << 16) | ('P' << 8) | '2') + +/* BSP2 support. 32bits instead of shorts for everything (bboxes use floats) */ +#define BSP2VERSION_BSP2 (('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24)) + +#define TOOLVERSION 2 + +typedef struct +{ + 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 +{ + 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 +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + 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 +{ + 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 +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +#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 + + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + 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 +} dsnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned int firstface; + unsigned int numfaces; // counting both sides +} dl1node_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + float mins[3]; // for sphere culling + float maxs[3]; + unsigned int firstface; + unsigned int numfaces; // counting both sides +} dl2node_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dsclipnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are contents +} dlclipnode_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 +#define TEX_MISSING 2 // johnfitz -- this texinfo does not have a texture + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dsedge_t; + +typedef struct +{ + unsigned int v[2]; // vertex numbers +} dledge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dsface_t; + +typedef struct +{ + int planenum; + int side; + + int firstedge; // we must support > 64k edges + int numedges; + int texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dlface_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 +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dsleaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned int firstmarksurface; + unsigned int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dl1leaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + float mins[3]; // for frustum culling + float maxs[3]; + + unsigned int firstmarksurface; + unsigned int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dl2leaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; + +extern int texdatasize; +extern byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +//extern int numleafs; +//extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numclipnodes; +extern dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int nummarksurfaces; +extern unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +#endif /* QUAKE_GAME */ + +#endif /* __BSPFILE_H */ + diff --git a/source/cd_null.c b/source/cd_null.c new file mode 100644 index 0000000..87afc7b --- /dev/null +++ b/source/cd_null.c @@ -0,0 +1,53 @@ +/* + * cd_null.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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "quakedef.h" + +int CDAudio_Play(byte track, qboolean looping) +{ + return -1; +} + +void CDAudio_Stop(void) +{ +} + +void CDAudio_Pause(void) +{ +} + +void CDAudio_Resume(void) +{ +} + +void CDAudio_Update(void) +{ +} + +int CDAudio_Init(void) +{ + Con_Printf("CDAudio disabled at compile time\n"); + return -1; +} + +void CDAudio_Shutdown(void) +{ +} + diff --git a/source/cd_sdl.c b/source/cd_sdl.c new file mode 100644 index 0000000..03f75e0 --- /dev/null +++ b/source/cd_sdl.c @@ -0,0 +1,585 @@ +/* + * cd_sdl.c + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Taken from the Twilight project with modifications + * to make it work with Hexen II: Hammer of Thyrion. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include + +#ifndef SDL_INIT_CDROM + +/* SDL dropped support for + cd audio since v1.3.0 */ +#pragma message("Warning: SDL CDAudio support disabled") +#include "cd_null.c" + +#else /* SDL_INIT_CDROM */ + +#include "quakedef.h" + +static qboolean cdValid = false; +static qboolean playing = false; +static qboolean wasPlaying = false; +static qboolean enabled = true; +static qboolean playLooping = false; +static byte remap[100]; +static byte playTrack; +static double endOfTrack = -1.0, pausetime = -1.0; +static SDL_CD *cd_handle; +static int cd_dev = -1; +static float old_cdvolume; +static qboolean hw_vol_works = true; + + +static void CDAudio_Eject(void) +{ + if (!cd_handle || !enabled) + return; + +#ifdef __linux__ + SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ +#endif + if (SDL_CDEject(cd_handle) < 0) + Con_Printf ("Unable to eject CD-ROM: %s\n", SDL_GetError ()); +} + +static int CDAudio_GetAudioDiskInfo(void) +{ + cdValid = false; + + if (!cd_handle) + return -1; + + if ( ! CD_INDRIVE(SDL_CDStatus(cd_handle)) ) + return -1; + + cdValid = true; + + return 0; +} + +int CDAudio_Play(byte track, qboolean looping) +{ + int len_m, len_s, len_f; + + if (!cd_handle || !enabled) + return -1; + + if (!cdValid) + { + CDAudio_GetAudioDiskInfo(); + if (!cdValid) + return -1; + } + + track = remap[track]; + + if (track < 1 || track > cd_handle->numtracks) + { + Con_Printf ("CDAudio_Play: Bad track number %d.\n", track); + return -1; + } + + if (cd_handle->track[track-1].type == SDL_DATA_TRACK) + { + Con_Printf ("CDAudio_Play: track %d is not audio\n", track); + return -1; + } + + if (playing) + { + if (playTrack == track) + return 0; + CDAudio_Stop(); + } + + if (SDL_CDPlay(cd_handle, cd_handle->track[track-1].offset, cd_handle->track[track-1].length) < 0) + { + Con_Printf ("CDAudio_Play: Unable to play track %d: %s\n", track, SDL_GetError ()); + return -1; + } + + playLooping = looping; + playTrack = track; + playing = true; + + FRAMES_TO_MSF(cd_handle->track[track-1].length, &len_m, &len_s, &len_f); + endOfTrack = realtime + ((double)len_m * 60.0) + (double)len_s + (double)len_f / (double)CD_FPS; + + /* Add the pregap for the next track. This means that disc-at-once CDs + * won't loop smoothly, but they wouldn't anyway so it doesn't really + * matter. SDL doesn't give us pregap information anyway, so you'll + * just have to live with it. */ + endOfTrack += 2.0; + pausetime = -1.0; + + if (bgmvolume.value == 0) /* don't bother advancing */ + CDAudio_Pause (); + + return 0; +} + +void CDAudio_Stop(void) +{ + if (!cd_handle || !enabled) + return; + + if (!playing) + return; + +#ifdef __linux__ + /* Don't really stop, but just pause: On some devices, the CDROMSTOP + * ioctl causes any followup ioctls to fail for a considerable time. + * observed with a TSSTcorp CDW/DVD SH-M522C drive with TS05 and TS08 + * firmware versions running under a 2.6.27.25 kernel, and with a + * Samsung DVD r/w drive running under 2.6.35.6 kernel. + * Therefore, avoid dead stops if playback may be resumed shortly. */ + if (SDL_CDPause(cd_handle) < 0) + Con_Printf ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); +#else + if (SDL_CDStop(cd_handle) < 0) + Con_Printf ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); +#endif + + wasPlaying = false; + playing = false; + pausetime = -1.0; + endOfTrack = -1.0; +} + +void CDAudio_Pause(void) +{ + if (!cd_handle || !enabled) + return; + + if (!playing) + return; + + if (SDL_CDPause(cd_handle) < 0) + Con_Printf ("Unable to pause CD-ROM: %s\n", SDL_GetError()); + + wasPlaying = playing; + playing = false; + pausetime = realtime; +} + +void CDAudio_Resume(void) +{ + if (!cd_handle || !enabled) + return; + + if (!cdValid) + return; + + if (!wasPlaying) + return; + + if (SDL_CDResume(cd_handle) < 0) + Con_Printf ("Unable to resume CD-ROM: %s\n", SDL_GetError()); + playing = true; + endOfTrack += realtime - pausetime; + pausetime = -1.0; +} + +static int get_first_audiotrk (void) +{ + int i; + for (i = 0; i < cd_handle->numtracks; ++i) + if (cd_handle->track[i].type != SDL_DATA_TRACK) + return ++i; + return 1; +} + +static void CD_f (void) +{ + const char *command; + int ret, n; + + if (Cmd_Argc() < 2) + { + Con_Printf("commands:"); + Con_Printf("on, off, reset, remap, \n"); + Con_Printf("play, stop, loop, pause, resume\n"); + Con_Printf("eject, info, next, prev\n"); + return; + } + + command = Cmd_Argv (1); + + if (q_strcasecmp(command, "on") == 0) + { + enabled = true; + return; + } + + if (q_strcasecmp(command, "off") == 0) + { + if (playing) + CDAudio_Stop(); + enabled = false; + return; + } + + if (q_strcasecmp(command, "reset") == 0) + { + enabled = true; + if (playing) + CDAudio_Stop(); + for (n = 0; n < 100; n++) + remap[n] = n; + CDAudio_GetAudioDiskInfo(); + return; + } + + if (q_strcasecmp(command, "remap") == 0) + { + ret = Cmd_Argc() - 2; + if (ret <= 0) + { + for (n = 1; n < 100; n++) + if (remap[n] != n) + Con_Printf(" %u -> %u\n", n, remap[n]); + return; + } + for (n = 1; n <= ret; n++) + remap[n] = atoi(Cmd_Argv (n + 1)); + return; + } + + if (!cdValid) + { + CDAudio_GetAudioDiskInfo(); + if (!cdValid) + { + Con_Printf("No CD in player.\n"); + return; + } + } + + if (q_strcasecmp(command, "play") == 0) + { + n = atoi(Cmd_Argv (2)); + if (n == 0) + n = 1; + CDAudio_Play((byte)n, false); + return; + } + + if (q_strcasecmp(command, "loop") == 0) + { + CDAudio_Play((byte)atoi(Cmd_Argv (2)), true); + return; + } + + if (q_strcasecmp(command, "stop") == 0) + { + CDAudio_Stop(); + return; + } + + if (q_strcasecmp(command, "pause") == 0) + { + CDAudio_Pause(); + return; + } + + if (q_strcasecmp(command, "resume") == 0) + { + CDAudio_Resume(); + return; + } + + if (q_strcasecmp(command, "eject") == 0) + { + if (playing) + CDAudio_Stop(); + CDAudio_Eject(); + cdValid = false; + return; + } + + if (q_strcasecmp(command, "info") == 0) + { + int current_min, current_sec, current_frame; + int length_min, length_sec, length_frame; + + Con_Printf ("%u tracks\n", cd_handle->numtracks); + + if (playing) + Con_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack); + else if (wasPlaying) + Con_Printf("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack); + + if (playing || wasPlaying) + { + SDL_CDStatus(cd_handle); + FRAMES_TO_MSF(cd_handle->cur_frame, ¤t_min, ¤t_sec, ¤t_frame); + FRAMES_TO_MSF(cd_handle->track[playTrack-1].length, &length_min, &length_sec, &length_frame); + + Con_Printf ("Current position: %d:%02d.%02d (of %d:%02d.%02d)\n", + current_min, current_sec, current_frame * 60 / CD_FPS, + length_min, length_sec, length_frame * 60 / CD_FPS); + } + Con_Printf("Volume is %f\n", bgmvolume.value); + + return; + } + + if (q_strcasecmp(command, "next") == 0) + { + if (playTrack == cd_handle->numtracks) + playTrack = get_first_audiotrk() - 1; + CDAudio_Play(playTrack + 1, playLooping); + return; + } + + if (q_strcasecmp(command, "prev") == 0) + { + if (playTrack == get_first_audiotrk()) + playTrack = cd_handle->numtracks + 1; + CDAudio_Play(playTrack - 1, playLooping); + return; + } + + Con_Printf ("cd: unknown command \"%s\"\n", command); +} + +static qboolean CD_GetVolume (void *unused) +{ +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return false; +} + +static qboolean CD_SetVolume (void *unused) +{ +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return false; +} + +static qboolean CDAudio_SetVolume (float value) +{ + if (!cd_handle || !enabled) + return false; + + old_cdvolume = value; + + if (value == 0.0f) + CDAudio_Pause (); + else + CDAudio_Resume(); + + if (!hw_vol_works) + { + return false; + } + else + { +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return CD_SetVolume (NULL); + } +} + +void CDAudio_Update(void) +{ + CDstatus curstat; +/* static double lastchk;*/ + + if (!cd_handle || !enabled) + return; + + if (old_cdvolume != bgmvolume.value) + CDAudio_SetVolume (bgmvolume.value); + +/* if (playing && realtime > lastchk)*/ + if (playing && realtime > endOfTrack) + { + /* lastchk = realtime + 2;*/ /* two seconds between chks */ + curstat = SDL_CDStatus(cd_handle); + if (curstat != CD_PLAYING && curstat != CD_PAUSED) + { + playing = false; + endOfTrack = -1.0; + if (playLooping) + CDAudio_Play(playTrack, true); + } + } +} + +static const char *get_cddev_arg (const char *arg) +{ +#if defined(_WIN32) +/* arg should be like "D:\", make sure it is so, + * but tolerate args like "D" or "D:", as well. */ + static char drive[4]; + if (!arg || ! *arg) + return NULL; + if (arg[1] != '\0') + { + if (arg[1] != ':') + return NULL; + if (arg[2] != '\0') + { + if (arg[2] != '\\' && + arg[2] != '/') + return NULL; + if (arg[3] != '\0') + return NULL; + } + } + if (*arg >= 'A' && *arg <= 'Z') + { + drive[0] = *arg; + drive[1] = ':'; + drive[2] = '\\'; + drive[3] = '\0'; + return drive; + } + else if (*arg >= 'a' && *arg <= 'z') + { + /* make it uppercase for SDL */ + drive[0] = *arg - ('a' - 'A'); + drive[1] = ':'; + drive[2] = '\\'; + drive[3] = '\0'; + return drive; + } + return NULL; +#else + if (!arg || ! *arg) + return NULL; + return arg; +#endif +} + +static void export_cddev_arg (void) +{ +/* Bad ugly hack to workaround SDL's cdrom device detection. + * not needed for windows due to the way SDL_cdrom works. */ +#if !defined(_WIN32) + int i = COM_CheckParm("-cddev"); + if (i != 0 && i < com_argc - 1 && com_argv[i+1][0] != '\0') + { + static char arg[64]; + q_snprintf(arg, sizeof(arg), "SDL_CDROM=%s", com_argv[i+1]); + putenv(arg); + } +#endif +} + +int CDAudio_Init(void) +{ + int i, sdl_num_drives; + + if (safemode || COM_CheckParm("-nocdaudio")) + return -1; + + export_cddev_arg(); + + if (SDL_InitSubSystem(SDL_INIT_CDROM) < 0) + { + Con_Printf("Couldn't init SDL cdrom: %s\n", SDL_GetError()); + return -1; + } + + sdl_num_drives = SDL_CDNumDrives (); + Con_Printf ("SDL detected %d CD-ROM drive%c\n", sdl_num_drives, + sdl_num_drives == 1 ? ' ' : 's'); + + if (sdl_num_drives < 1) + return -1; + + if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) + { + const char *userdev = get_cddev_arg(com_argv[i+1]); + if (!userdev) + { + Con_Printf("Invalid argument to -cddev\n"); + return -1; + } + for (i = 0; i < sdl_num_drives; i++) + { + if (!q_strcasecmp(SDL_CDName(i), userdev)) + { + cd_dev = i; + break; + } + } + if (cd_dev == -1) + { + Con_Printf("SDL couldn't find cdrom device %s\n", userdev); + return -1; + } + } + + if (cd_dev == -1) + cd_dev = 0; /* default drive */ + + cd_handle = SDL_CDOpen(cd_dev); + if (!cd_handle) + { + Con_Printf ("CDAudio_Init: Unable to open CD-ROM drive %s (%s)\n", + SDL_CDName(cd_dev), SDL_GetError()); + return -1; + } + + for (i = 0; i < 100; i++) + remap[i] = i; + enabled = true; + old_cdvolume = bgmvolume.value; + + Con_Printf("CDAudio initialized (SDL, using %s)\n", SDL_CDName(cd_dev)); + + if (CDAudio_GetAudioDiskInfo()) + { + Con_Printf("CDAudio_Init: No CD in drive\n"); + cdValid = false; + } + + Cmd_AddCommand ("cd", CD_f); + + hw_vol_works = CD_GetVolume (NULL); /* no SDL support at present. */ + if (hw_vol_works) + hw_vol_works = CDAudio_SetVolume (bgmvolume.value); + + return 0; +} + +void CDAudio_Shutdown(void) +{ + if (!cd_handle) + return; + CDAudio_Stop(); + if (hw_vol_works) + CD_SetVolume (NULL); /* no SDL support at present. */ +#ifdef __linux__ + SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ +#endif + SDL_CDClose(cd_handle); + cd_handle = NULL; + cd_dev = -1; + SDL_QuitSubSystem(SDL_INIT_CDROM); +} + +#endif /* SDL_INIT_CDROM */ + diff --git a/source/cdaudio.h b/source/cdaudio.h new file mode 100644 index 0000000..7c8477f --- /dev/null +++ b/source/cdaudio.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __CDAUDIO_H +#define __CDAUDIO_H + +int CDAudio_Init (void); +int CDAudio_Play (byte track, qboolean looping); + /* returns 0 for success, -1 for failure. */ +void CDAudio_Stop (void); +void CDAudio_Pause (void); +void CDAudio_Resume (void); +void CDAudio_Shutdown (void); +void CDAudio_Update (void); + +#endif /* __CDAUDIO_H */ + diff --git a/source/cfgfile.c b/source/cfgfile.c new file mode 100644 index 0000000..3373db4 --- /dev/null +++ b/source/cfgfile.c @@ -0,0 +1,175 @@ +/* + * cfgfile.c -- misc reads from the config file + * + * Copyright (C) 2008-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + + +static fshandle_t *cfg_file; + +/* +=================== +CFG_ReadCvars + +used for doing early reads from config.cfg searching the list +of given cvar names for the user-set values. a temporary +solution until we merge a better cvar system. +the num_vars argument must be the exact number of strings in the +array, otherwise I have nothing against going out of bounds. +=================== +*/ +void CFG_ReadCvars (const char **vars, int num_vars) +{ + char buff[1024], *tmp; + int i, j; + + if (!cfg_file || num_vars < 1) + return; + + j = 0; + + do { + i = 0; + memset (buff, 0, sizeof(buff)); + // we expect a line in the format that Cvar_WriteVariables + // writes to the config file. although I'm trying to be as + // much cautious as possible, if the user screws it up by + // editing it, it's his fault. + if (FS_fgets(buff, sizeof(buff), cfg_file)) + { + // remove end-of-line characters + while (buff[i]) + { + if (buff[i] == '\r' || buff[i] == '\n') + buff[i] = '\0'; + // while we're here, replace tabs with spaces + if (buff[i] == '\t') + buff[i] = ' '; + i++; + } + // go to the last character + while (buff[i] == 0 && i > 0) + i--; + // remove trailing spaces + while (i > 0) + { + if (buff[i] == ' ') + { + buff[i] = '\0'; + i--; + } + else + break; + } + + // the line must end with a quotation mark + if (buff[i] != '\"') + continue; + buff[i] = '\0'; + + for (i = 0; i < num_vars && vars[i]; i++) + { + // look for the cvar name + one space + tmp = strstr(buff, va("%s ",vars[i])); + if (tmp != buff) + continue; + // locate the first quotation mark + tmp = strchr(buff, '\"'); + if (tmp) + { + Cvar_Set (vars[i], tmp + 1); + j++; + break; + } + } + } + + if (j == num_vars) + break; + + } while (!FS_feof(cfg_file) && !FS_ferror(cfg_file)); + + FS_rewind (cfg_file); +} + +/* +=================== +CFG_ReadCvarOverrides + +convenience function, reading the "+" command line override +values of cvars in the given list. doesn't do anything with +the config file. +=================== +*/ +void CFG_ReadCvarOverrides (const char **vars, int num_vars) +{ + char buff[64]; + int i, j; + + if (num_vars < 1) + return; + + buff[0] = '+'; + + for (i = 0; i < num_vars; i++) + { + q_strlcpy (&buff[1], vars[i], sizeof(buff) - 1); + j = COM_CheckParm(buff); + if (j != 0 && j < com_argc - 1) + { + if (com_argv[j + 1][0] != '-' && com_argv[j + 1][0] != '+') + Cvar_Set(vars[i], com_argv[j + 1]); + } + } +} + +void CFG_CloseConfig (void) +{ + if (cfg_file) + { + FS_fclose(cfg_file); + Z_Free(cfg_file); + cfg_file = NULL; + } +} + +int CFG_OpenConfig (const char *cfg_name) +{ + FILE *f; + long length; + qboolean pak; + + CFG_CloseConfig (); + + length = (long) COM_FOpenFile (cfg_name, &f, NULL); + pak = file_from_pak; + if (length == -1) + return -1; + + cfg_file = (fshandle_t *) Z_Malloc(sizeof(fshandle_t)); + cfg_file->file = f; + cfg_file->start = ftell(f); + cfg_file->pos = 0; + cfg_file->length = length; + cfg_file->pak = pak; + + return 0; +} + diff --git a/source/cfgfile.h b/source/cfgfile.h new file mode 100644 index 0000000..dbe8f39 --- /dev/null +++ b/source/cfgfile.h @@ -0,0 +1,42 @@ +/* + * cfgfile.h -- misc reads from the config file + * + * Copyright (C) 2008-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CFGFILE_H +#define __CFGFILE_H + +int CFG_OpenConfig (const char *cfg_name); +// opens the given config file. only one open config file is +// kept: previosly opened one, if any, will be closed. + +void CFG_CloseConfig (void); +// closes the currently open config file. + +void CFG_ReadCvars (const char **vars, int num_vars); +// reads the values of cvars in the given list from the opened +// config file. + +void CFG_ReadCvarOverrides (const char **vars, int num_vars); +// convenience function, reading the "+" command line override +// values of cvars in the given list. doesn't do anything with +// the config file. call this after CFG_ReadCvars() and before +// locking your cvars. + +#endif /* __CFGFILE_H */ diff --git a/source/chase.c b/source/chase.c new file mode 100644 index 0000000..6ddd25b --- /dev/null +++ b/source/chase.c @@ -0,0 +1,118 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +// chase.c -- chase camera code + +#include "quakedef.h" + +cvar_t chase_back = {"chase_back", "100", CVAR_NONE}; +cvar_t chase_up = {"chase_up", "16", CVAR_NONE}; +cvar_t chase_right = {"chase_right", "0", CVAR_NONE}; +cvar_t chase_active = {"chase_active", "0", CVAR_NONE}; + +/* +============== +Chase_Init +============== +*/ +void Chase_Init (void) +{ + Cvar_RegisterVariable (&chase_back); + Cvar_RegisterVariable (&chase_up); + Cvar_RegisterVariable (&chase_right); + Cvar_RegisterVariable (&chase_active); +} + +/* +============== +TraceLine + +TODO: impact on bmodels, monsters +============== +*/ +void TraceLine (vec3_t start, vec3_t end, vec3_t impact) +{ + trace_t trace; + + memset (&trace, 0, sizeof(trace)); + SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace); + + VectorCopy (trace.endpos, impact); +} + +/* +============== +Chase_UpdateForClient -- johnfitz -- orient client based on camera. called after input +============== +*/ +void Chase_UpdateForClient (void) +{ + //place camera + + //assign client angles to camera + + //see where camera points + + //adjust client angles to point at the same place +} + +/* +============== +Chase_UpdateForDrawing -- johnfitz -- orient camera based on client. called before drawing + +TODO: stay at least 8 units away from all walls in this leaf +============== +*/ +void Chase_UpdateForDrawing (void) +{ + int i; + vec3_t forward, up, right; + vec3_t ideal, crosshair, temp; + + AngleVectors (cl.viewangles, forward, right, up); + + // calc ideal camera location before checking for walls + for (i=0 ; i<3 ; i++) + ideal[i] = cl.viewent.origin[i] + - forward[i]*chase_back.value + + right[i]*chase_right.value; + //+ up[i]*chase_up.value; + ideal[2] = cl.viewent.origin[2] + chase_up.value; + + // make sure camera is not in or behind a wall + TraceLine(r_refdef.vieworg, ideal, temp); + if (VectorLength(temp) != 0) + VectorCopy(temp, ideal); + + // place camera + VectorCopy (ideal, r_refdef.vieworg); + + // find the spot the player is looking at + VectorMA (cl.viewent.origin, 4096, forward, temp); + TraceLine (cl.viewent.origin, temp, crosshair); + + // calculate camera angles to look at the same spot + VectorSubtract (crosshair, r_refdef.vieworg, temp); + VectorAngles (temp, r_refdef.viewangles); + if (r_refdef.viewangles[PITCH] == 90 || r_refdef.viewangles[PITCH] == -90) + r_refdef.viewangles[YAW] = cl.viewangles[YAW]; +} + diff --git a/source/cl_demo.c b/source/cl_demo.c new file mode 100644 index 0000000..d1ad2bf --- /dev/null +++ b/source/cl_demo.c @@ -0,0 +1,504 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +static void CL_FinishTimeDemo (void); + +/* +============================================================================== + +DEMO CODE + +When a demo is playing back, all NET_SendMessages are skipped, and +NET_GetMessages are read from the demo file. + +Whenever cl.time gets past the last received message, another message is +read from the demo file. +============================================================================== +*/ + +// from ProQuake: space to fill out the demo header for record at any time +static byte demo_head[3][MAX_MSGLEN]; +static int demo_head_size[2]; + +/* +============== +CL_StopPlayback + +Called when a demo file runs out, or the user starts a game +============== +*/ +void CL_StopPlayback (void) +{ + if (!cls.demoplayback) + return; + + fclose (cls.demofile); + cls.demoplayback = false; + cls.demopaused = false; + cls.demofile = NULL; + cls.state = ca_disconnected; + + if (cls.timedemo) + CL_FinishTimeDemo (); +} + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length and view angles +==================== +*/ +static void CL_WriteDemoMessage (void) +{ + int len; + int i; + float f; + + len = LittleLong (net_message.cursize); + fwrite (&len, 4, 1, cls.demofile); + for (i = 0; i < 3; i++) + { + f = LittleFloat (cl.viewangles[i]); + fwrite (&f, 4, 1, cls.demofile); + } + fwrite (net_message.data, net_message.cursize, 1, cls.demofile); + fflush (cls.demofile); +} + +static int CL_GetDemoMessage (void) +{ + int r, i; + float f; + + if (cls.demopaused) + return 0; + + // decide if it is time to grab the next message + if (cls.signon == SIGNONS) // always grab until fully connected + { + if (cls.timedemo) + { + if (host_framecount == cls.td_lastframe) + return 0; // already read this frame's message + cls.td_lastframe = host_framecount; + // if this is the second frame, grab the real td_starttime + // so the bogus time on the first frame doesn't count + if (host_framecount == cls.td_startframe + 1) + cls.td_starttime = realtime; + } + else if (/* cl.time > 0 && */ cl.time <= cl.mtime[0]) + { + return 0; // don't need another message yet + } + } + +// get the next message + fread (&net_message.cursize, 4, 1, cls.demofile); + VectorCopy (cl.mviewangles[0], cl.mviewangles[1]); + for (i = 0 ; i < 3 ; i++) + { + r = fread (&f, 4, 1, cls.demofile); + cl.mviewangles[0][i] = LittleFloat (f); + } + + net_message.cursize = LittleLong (net_message.cursize); + if (net_message.cursize > MAX_MSGLEN) + Sys_Error ("Demo message > MAX_MSGLEN"); + r = fread (net_message.data, net_message.cursize, 1, cls.demofile); + if (r != 1) + { + CL_StopPlayback (); + return 0; + } + + return 1; +} + +/* +==================== +CL_GetMessage + +Handles recording and playback of demos, on top of NET_ code +==================== +*/ +int CL_GetMessage (void) +{ + int r; + + if (cls.demoplayback) + return CL_GetDemoMessage (); + + while (1) + { + r = NET_GetMessage (cls.netcon); + + if (r != 1 && r != 2) + return r; + + // discard nop keepalive message + if (net_message.cursize == 1 && net_message.data[0] == svc_nop) + Con_Printf ("<-- server to client keepalive\n"); + else + break; + } + + if (cls.demorecording) + CL_WriteDemoMessage (); + + if (cls.signon < 2) + { + // record messages before full connection, so that a + // demo record can happen after connection is done + memcpy(demo_head[cls.signon], net_message.data, net_message.cursize); + demo_head_size[cls.signon] = net_message.cursize; + } + + return r; +} + + +/* +==================== +CL_Stop_f + +stop recording a demo +==================== +*/ +void CL_Stop_f (void) +{ + if (cmd_source != src_command) + return; + + if (!cls.demorecording) + { + Con_Printf ("Not recording a demo.\n"); + return; + } + +// write a disconnect message to the demo file + SZ_Clear (&net_message); + MSG_WriteByte (&net_message, svc_disconnect); + CL_WriteDemoMessage (); + +// finish up + fclose (cls.demofile); + cls.demofile = NULL; + cls.demorecording = false; + Con_Printf ("Completed demo\n"); + +// ericw -- update demo tab-completion list + DemoList_Rebuild (); +} + +/* +==================== +CL_Record_f + +record [cd track] +==================== +*/ +void CL_Record_f (void) +{ + int c; + char name[MAX_OSPATH]; + int track; + + if (cmd_source != src_command) + return; + + if (cls.demoplayback) + { + Con_Printf ("Can't record during demo playback\n"); + return; + } + + if (cls.demorecording) + CL_Stop_f(); + + c = Cmd_Argc(); + if (c != 2 && c != 3 && c != 4) + { + Con_Printf ("record [ [cd track]]\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + if (c == 2 && cls.state == ca_connected) + { +#if 0 + Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); + return; +#endif + if (cls.signon < 2) + { + Con_Printf("Can't record - try again when connected\n"); + return; + } + } + +// 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; + } + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + +// start the map up + if (c > 2) + { + Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command); + if (cls.state != ca_connected) + return; + } + +// open the demo file + COM_AddExtension (name, ".dem", sizeof(name)); + + Con_Printf ("recording to %s.\n", name); + cls.demofile = fopen (name, "wb"); + if (!cls.demofile) + { + Con_Printf ("ERROR: couldn't create %s\n", name); + return; + } + + cls.forcetrack = track; + fprintf (cls.demofile, "%i\n", cls.forcetrack); + + cls.demorecording = true; + + // from ProQuake: initialize the demo file if we're already connected + if (c == 2 && cls.state == ca_connected) + { + byte *data = net_message.data; + int cursize = net_message.cursize; + int i; + + for (i = 0; i < 2; i++) + { + net_message.data = demo_head[i]; + net_message.cursize = demo_head_size[i]; + CL_WriteDemoMessage(); + } + + net_message.data = demo_head[2]; + SZ_Clear (&net_message); + + // current names, colors, and frag counts + for (i = 0; i < cl.maxclients; i++) + { + MSG_WriteByte (&net_message, svc_updatename); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl.scores[i].name); + MSG_WriteByte (&net_message, svc_updatefrags); + MSG_WriteByte (&net_message, i); + MSG_WriteShort (&net_message, cl.scores[i].frags); + MSG_WriteByte (&net_message, svc_updatecolors); + MSG_WriteByte (&net_message, i); + MSG_WriteByte (&net_message, cl.scores[i].colors); + } + + // send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + MSG_WriteByte (&net_message, svc_lightstyle); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl_lightstyle[i].map); + } + + // what about the CD track or SVC fog... future consideration. + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_TOTALSECRETS); + MSG_WriteLong (&net_message, cl.stats[STAT_TOTALSECRETS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_TOTALMONSTERS); + MSG_WriteLong (&net_message, cl.stats[STAT_TOTALMONSTERS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_SECRETS); + MSG_WriteLong (&net_message, cl.stats[STAT_SECRETS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_MONSTERS); + MSG_WriteLong (&net_message, cl.stats[STAT_MONSTERS]); + + // view entity + MSG_WriteByte (&net_message, svc_setview); + MSG_WriteShort (&net_message, cl.viewentity); + + // signon + MSG_WriteByte (&net_message, svc_signonnum); + MSG_WriteByte (&net_message, 3); + + CL_WriteDemoMessage(); + + // restore net_message + net_message.data = data; + net_message.cursize = cursize; + } +} + + +/* +==================== +CL_PlayDemo_f + +play [demoname] +==================== +*/ +void CL_PlayDemo_f (void) +{ + char name[MAX_OSPATH]; + int i, c; + qboolean neg; + + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("playdemo : plays a demo\n"); + return; + } + +// disconnect from server + CL_Disconnect (); + +// open the demo file + q_strlcpy (name, Cmd_Argv(1), sizeof(name)); + COM_AddExtension (name, ".dem", sizeof(name)); + + Con_Printf ("Playing demo from %s.\n", name); + + COM_FOpenFile (name, &cls.demofile, NULL); + if (!cls.demofile) + { + Con_Printf ("ERROR: couldn't open %s\n", name); + cls.demonum = -1; // stop demo loop + return; + } + +// ZOID, fscanf is evil +// O.S.: if a space character e.g. 0x20 (' ') follows '\n', +// fscanf skips that byte too and screws up further reads. +// fscanf (cls.demofile, "%i\n", &cls.forcetrack); + cls.forcetrack = 0; + c = 0; /* silence pesky compiler warnings */ + neg = false; + // read a decimal integer possibly with a leading '-', + // followed by a '\n': + for (i = 0; i < 13; i++) + { + c = getc(cls.demofile); + if (c == '\n') + break; + if (c == '-') { + neg = true; + continue; + } + // check for multiple '-' or legal digits? meh... + cls.forcetrack = cls.forcetrack * 10 + (c - '0'); + } + if (c != '\n') + { + fclose (cls.demofile); + cls.demofile = NULL; + cls.demonum = -1; // stop demo loop + Con_Printf ("ERROR: demo \"%s\" is invalid\n", name); + return; + } + if (neg) + cls.forcetrack = -cls.forcetrack; + + cls.demoplayback = true; + cls.demopaused = false; + cls.state = ca_connected; + +// get rid of the menu and/or console + key_dest = key_game; +} + +/* +==================== +CL_FinishTimeDemo + +==================== +*/ +static void CL_FinishTimeDemo (void) +{ + int frames; + float time; + + cls.timedemo = false; + +// the first frame didn't count + frames = (host_framecount - cls.td_startframe) - 1; + time = realtime - cls.td_starttime; + if (!time) + time = 1; + Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time); +} + +/* +==================== +CL_TimeDemo_f + +timedemo [demoname] +==================== +*/ +void CL_TimeDemo_f (void) +{ + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("timedemo : gets demo speeds\n"); + return; + } + + CL_PlayDemo_f (); + if (!cls.demofile) + return; + +// cls.td_starttime will be grabbed at the second frame of the demo, so +// all the loading time doesn't get counted + + cls.timedemo = true; + cls.td_startframe = host_framecount; + cls.td_lastframe = -1; // get a new message this frame +} + diff --git a/source/cl_input.c b/source/cl_input.c new file mode 100644 index 0000000..6e5fcdb --- /dev/null +++ b/source/cl_input.c @@ -0,0 +1,454 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +extern cvar_t cl_maxpitch; //johnfitz -- variable pitch clamping +extern cvar_t cl_minpitch; //johnfitz -- variable pitch clamping + +/* +=============================================================================== + +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_use, in_jump, in_attack; +kbutton_t in_up, in_down; + +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_Printf ("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);} +void IN_JumpDown (void) {KeyDown(&in_jump);} +void IN_JumpUp (void) {KeyUp(&in_jump);} + +void IN_Impulse (void) {in_impulse=Q_atoi(Cmd_Argv(1));} + +/* +=============== +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; + impulseup = key->state & 4; + down = key->state & 1; + 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 = {"cl_upspeed","200",CVAR_NONE}; +cvar_t cl_forwardspeed = {"cl_forwardspeed","200", CVAR_ARCHIVE}; +cvar_t cl_backspeed = {"cl_backspeed","200", CVAR_ARCHIVE}; +cvar_t cl_sidespeed = {"cl_sidespeed","350",CVAR_NONE}; + +cvar_t cl_movespeedkey = {"cl_movespeedkey","2.0",CVAR_NONE}; + +cvar_t cl_yawspeed = {"cl_yawspeed","140",CVAR_NONE}; +cvar_t cl_pitchspeed = {"cl_pitchspeed","150",CVAR_NONE}; + +cvar_t cl_anglespeedkey = {"cl_anglespeedkey","1.5",CVAR_NONE}; + +cvar_t cl_alwaysrun = {"cl_alwaysrun","0",CVAR_ARCHIVE}; // QuakeSpasm -- new always run + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles (void) +{ + float speed; + float up, down; + + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0)) + speed = host_frametime * cl_anglespeedkey.value; + else + speed = host_frametime; + + if (!(in_strafe.state & 1)) + { + cl.viewangles[YAW] -= speed*cl_yawspeed.value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed.value*CL_KeyState (&in_left); + cl.viewangles[YAW] = anglemod(cl.viewangles[YAW]); + } + 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 (); + + //johnfitz -- variable pitch clamping + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; + //johnfitz + + if (cl.viewangles[ROLL] > 50) + cl.viewangles[ROLL] = 50; + if (cl.viewangles[ROLL] < -50) + cl.viewangles[ROLL] = -50; +} + +/* +================ +CL_BaseMove + +Send the intended movement message to the server +================ +*/ +void CL_BaseMove (usercmd_t *cmd) +{ + if (cls.signon != SIGNONS) + return; + + CL_AdjustAngles (); + + Q_memset (cmd, 0, sizeof(*cmd)); + + if (in_strafe.state & 1) + { + cmd->sidemove += cl_sidespeed.value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed.value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed.value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed.value * CL_KeyState (&in_down); + + if (! (in_klook.state & 1) ) + { + cmd->forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed.value * CL_KeyState (&in_back); + } + +// +// adjust for speed key +// + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0)) + { + cmd->forwardmove *= cl_movespeedkey.value; + cmd->sidemove *= cl_movespeedkey.value; + cmd->upmove *= cl_movespeedkey.value; + } +} + + +/* +============== +CL_SendMove +============== +*/ +void CL_SendMove (const usercmd_t *cmd) +{ + int i; + int bits; + sizebuf_t buf; + byte data[128]; + + buf.maxsize = 128; + buf.cursize = 0; + buf.data = data; + + cl.cmd = *cmd; + +// +// send the movement message +// + MSG_WriteByte (&buf, clc_move); + + MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + + for (i=0 ; i<3 ; i++) + //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + if (cl.protocol == PROTOCOL_NETQUAKE) + MSG_WriteAngle (&buf, cl.viewangles[i], cl.protocolflags); + else + MSG_WriteAngle16 (&buf, cl.viewangles[i], cl.protocolflags); + //johnfitz + + MSG_WriteShort (&buf, cmd->forwardmove); + MSG_WriteShort (&buf, cmd->sidemove); + MSG_WriteShort (&buf, cmd->upmove); + +// +// send button bits +// + bits = 0; + + if ( in_attack.state & 3 ) + bits |= 1; + in_attack.state &= ~2; + + if (in_jump.state & 3) + bits |= 2; + in_jump.state &= ~2; + + MSG_WriteByte (&buf, bits); + + MSG_WriteByte (&buf, in_impulse); + in_impulse = 0; + +// +// deliver the message +// + if (cls.demoplayback) + return; + +// +// allways dump the first two message, because it may contain leftover inputs +// from the last level +// + if (++cl.movemessages <= 2) + return; + + if (NET_SendUnreliableMessage (cls.netcon, &buf) == -1) + { + Con_Printf ("CL_SendMove: lost server connection\n"); + CL_Disconnect (); + } +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput (void) +{ + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_AttackDown); + Cmd_AddCommand ("-attack", IN_AttackUp); + Cmd_AddCommand ("+use", IN_UseDown); + Cmd_AddCommand ("-use", IN_UseUp); + Cmd_AddCommand ("+jump", IN_JumpDown); + Cmd_AddCommand ("-jump", IN_JumpUp); + Cmd_AddCommand ("impulse", IN_Impulse); + Cmd_AddCommand ("+klook", IN_KLookDown); + Cmd_AddCommand ("-klook", IN_KLookUp); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + +} + diff --git a/source/cl_main.c b/source/cl_main.c new file mode 100644 index 0000000..5d870bd --- /dev/null +++ b/source/cl_main.c @@ -0,0 +1,817 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "bgmusic.h" + +// we need to declare some mouse variables here, because the menu system +// references them even when on a unix system. + +// these two are not intended to be set directly +cvar_t cl_name = {"_cl_name", "player", CVAR_ARCHIVE}; +cvar_t cl_color = {"_cl_color", "0", CVAR_ARCHIVE}; + +cvar_t cl_shownet = {"cl_shownet","0",CVAR_NONE}; // can be 0, 1, or 2 +cvar_t cl_nolerp = {"cl_nolerp","0",CVAR_NONE}; + +cvar_t cfg_unbindall = {"cfg_unbindall", "1", CVAR_ARCHIVE}; + +cvar_t lookspring = {"lookspring","0", CVAR_ARCHIVE}; +cvar_t lookstrafe = {"lookstrafe","0", CVAR_ARCHIVE}; +cvar_t sensitivity = {"sensitivity","3", CVAR_ARCHIVE}; + +cvar_t m_pitch = {"m_pitch","0.022", CVAR_ARCHIVE}; +cvar_t m_yaw = {"m_yaw","0.022", CVAR_ARCHIVE}; +cvar_t m_forward = {"m_forward","1", CVAR_ARCHIVE}; +cvar_t m_side = {"m_side","0.8", CVAR_ARCHIVE}; + +cvar_t cl_maxpitch = {"cl_maxpitch", "90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping +cvar_t cl_minpitch = {"cl_minpitch", "-90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping + +client_static_t cls; +client_state_t cl; +// FIXME: put these on hunk? +entity_t cl_static_entities[MAX_STATIC_ENTITIES]; +lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; +dlight_t cl_dlights[MAX_DLIGHTS]; + +entity_t *cl_entities; //johnfitz -- was a static array, now on hunk +int cl_max_edicts; //johnfitz -- only changes when new map loads + +int cl_numvisedicts; +entity_t *cl_visedicts[MAX_VISEDICTS]; + +extern cvar_t r_lerpmodels, r_lerpmove; //johnfitz + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState (void) +{ + if (!sv.active) + Host_ClearMemory (); + +// wipe the entire cl structure + memset (&cl, 0, sizeof(cl)); + + SZ_Clear (&cls.message); + +// clear other arrays + memset (cl_dlights, 0, sizeof(cl_dlights)); + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + memset (cl_temp_entities, 0, sizeof(cl_temp_entities)); + memset (cl_beams, 0, sizeof(cl_beams)); + + //johnfitz -- cl_entities is now dynamically allocated + cl_max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); + cl_entities = (entity_t *) Hunk_AllocName (cl_max_edicts*sizeof(entity_t), "cl_entities"); + //johnfitz +} + +/* +===================== +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 (key_dest == key_message) + Key_EndChat (); // don't get stuck in chat mode + +// stop sounds (especially looping!) + S_StopAllSounds (true); + BGM_Stop(); + CDAudio_Stop(); + +// if running a local server, shut it down + if (cls.demoplayback) + CL_StopPlayback (); + else if (cls.state == ca_connected) + { + if (cls.demorecording) + CL_Stop_f (); + + Con_DPrintf ("Sending clc_disconnect\n"); + SZ_Clear (&cls.message); + MSG_WriteByte (&cls.message, clc_disconnect); + NET_SendUnreliableMessage (cls.netcon, &cls.message); + SZ_Clear (&cls.message); + NET_Close (cls.netcon); + + cls.state = ca_disconnected; + if (sv.active) + Host_ShutdownServer(false); + } + + cls.demoplayback = cls.timedemo = false; + cls.demopaused = false; + cls.signon = 0; + cl.intermission = 0; +} + +void CL_Disconnect_f (void) +{ + CL_Disconnect (); + if (sv.active) + Host_ShutdownServer (false); +} + + +/* +===================== +CL_EstablishConnection + +Host should be either "local" or a net address to be passed on +===================== +*/ +void CL_EstablishConnection (const char *host) +{ + if (cls.state == ca_dedicated) + return; + + if (cls.demoplayback) + return; + + CL_Disconnect (); + + cls.netcon = NET_Connect (host); + if (!cls.netcon) + Host_Error ("CL_Connect: connect failed\n"); + Con_DPrintf ("CL_EstablishConnection: connected to %s\n", host); + + cls.demonum = -1; // not in the demo loop now + cls.state = ca_connected; + cls.signon = 0; // need all the signon messages before playing + MSG_WriteByte (&cls.message, clc_nop); // NAT Fix from ProQuake +} + +/* +===================== +CL_SignonReply + +An svc_signonnum has been received, perform a client side setup +===================== +*/ +void CL_SignonReply (void) +{ + char str[8192]; + + Con_DPrintf ("CL_SignonReply: %i\n", cls.signon); + + switch (cls.signon) + { + case 1: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "prespawn"); + break; + + case 2: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("name \"%s\"\n", cl_name.string)); + + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("color %i %i\n", ((int)cl_color.value)>>4, ((int)cl_color.value)&15)); + + MSG_WriteByte (&cls.message, clc_stringcmd); + sprintf (str, "spawn %s", cls.spawnparms); + MSG_WriteString (&cls.message, str); + break; + + case 3: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "begin"); + Cache_Report (); // print remaining memory + break; + + case 4: + SCR_EndLoadingPlaque (); // allow normal screen updates + break; + } +} + +/* +===================== +CL_NextDemo + +Called to play the next demo in the demo loop +===================== +*/ +void CL_NextDemo (void) +{ + char str[1024]; + + 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_Printf ("No demos listed with startdemos\n"); + cls.demonum = -1; + CL_Disconnect(); + return; + } + } + + SCR_BeginLoadingPlaque (); + + sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]); + Cbuf_InsertText (str); + cls.demonum++; +} + +/* +============== +CL_PrintEntities_f +============== +*/ +void CL_PrintEntities_f (void) +{ + entity_t *ent; + int i; + + if (cls.state != ca_connected) + return; + + for (i=0,ent=cl_entities ; imodel) + { + Con_Printf ("EMPTY\n"); + continue; + } + Con_Printf ("%s:%2i (%5.1f,%5.1f,%5.1f) [%5.1f %5.1f %5.1f]\n" + ,ent->model->name,ent->frame, ent->origin[0], ent->origin[1], ent->origin[2], ent->angles[0], ent->angles[1], ent->angles[2]); + } +} + +/* +=============== +CL_AllocDlight + +=============== +*/ +dlight_t *CL_AllocDlight (int key) +{ + int i; + dlight_t *dl; + +// first look for an exact key match + if (key) + { + dl = cl_dlights; + for (i=0 ; ikey == key) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; + } + } + } + +// then look for anything else + dl = cl_dlights; + for (i=0 ; idie < cl.time) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; + } + } + + dl = &cl_dlights[0]; + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; +} + + +/* +=============== +CL_DecayLights + +=============== +*/ +void CL_DecayLights (void) +{ + int i; + dlight_t *dl; + float time; + + time = cl.time - cl.oldtime; + + dl = cl_dlights; + for (i=0 ; idie < cl.time || !dl->radius) + continue; + + dl->radius -= time*dl->decay; + if (dl->radius < 0) + dl->radius = 0; + } +} + + +/* +=============== +CL_LerpPoint + +Determines the fraction between the last two messages that the objects +should be put at. +=============== +*/ +float CL_LerpPoint (void) +{ + float f, frac; + + f = cl.mtime[0] - cl.mtime[1]; + + if (!f || cls.timedemo || sv.active) + { + cl.time = cl.mtime[0]; + return 1; + } + + if (f > 0.1) // dropped packet, or start of demo + { + cl.mtime[1] = cl.mtime[0] - 0.1; + f = 0.1; + } + + frac = (cl.time - cl.mtime[1]) / f; + + if (frac < 0) + { + if (frac < -0.01) + cl.time = cl.mtime[1]; + frac = 0; + } + else if (frac > 1) + { + if (frac > 1.01) + cl.time = cl.mtime[0]; + frac = 1; + } + + //johnfitz -- better nolerp behavior + if (cl_nolerp.value) + return 1; + //johnfitz + + return frac; +} + +/* +=============== +CL_RelinkEntities +=============== +*/ +void CL_RelinkEntities (void) +{ + entity_t *ent; + int i, j; + float frac, f, d; + vec3_t delta; + float bobjrotate; + vec3_t oldorg; + dlight_t *dl; + +// determine partial update time + frac = CL_LerpPoint (); + + cl_numvisedicts = 0; + +// +// interpolate player info +// + for (i=0 ; i<3 ; i++) + cl.velocity[i] = cl.mvelocity[1][i] + + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); + + if (cls.demoplayback) + { + // interpolate the angles + for (j=0 ; j<3 ; j++) + { + d = cl.mviewangles[0][j] - cl.mviewangles[1][j]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + cl.viewangles[j] = cl.mviewangles[1][j] + frac*d; + } + } + + bobjrotate = anglemod(100*cl.time); + +// start on the entity after the world + for (i=1,ent=cl_entities+1 ; imodel) + { // empty slot + + // ericw -- efrags are only used for static entities in GLQuake + // ent can't be static, so this is a no-op. + //if (ent->forcelink) + // R_RemoveEfrags (ent); // just became empty + continue; + } + +// if the object wasn't included in the last packet, remove it + if (ent->msgtime != cl.mtime[0]) + { + ent->model = NULL; + ent->lerpflags |= LERP_RESETMOVE|LERP_RESETANIM; //johnfitz -- next time this entity slot is reused, the lerp will need to be reset + continue; + } + + VectorCopy (ent->origin, oldorg); + + if (ent->forcelink) + { // the entity was not updated in the last message + // so move to the final spot + VectorCopy (ent->msg_origins[0], ent->origin); + VectorCopy (ent->msg_angles[0], ent->angles); + } + else + { // if the delta is large, assume a teleport and don't lerp + f = frac; + for (j=0 ; j<3 ; j++) + { + delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j]; + if (delta[j] > 100 || delta[j] < -100) + { + f = 1; // assume a teleportation, not a motion + ent->lerpflags |= LERP_RESETMOVE; //johnfitz -- don't lerp teleports + } + } + + //johnfitz -- don't cl_lerp entities that will be r_lerped + if (r_lerpmove.value && (ent->lerpflags & LERP_MOVESTEP)) + f = 1; + //johnfitz + + // interpolate the origin and angles + for (j=0 ; j<3 ; j++) + { + ent->origin[j] = ent->msg_origins[1][j] + f*delta[j]; + + d = ent->msg_angles[0][j] - ent->msg_angles[1][j]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + ent->angles[j] = ent->msg_angles[1][j] + f*d; + } + } + +// rotate binary objects locally + if (ent->model->flags & EF_ROTATE) + ent->angles[1] = bobjrotate; + + if (ent->effects & EF_BRIGHTFIELD) + R_EntityParticles (ent); + + if (ent->effects & EF_MUZZLEFLASH) + { + vec3_t fv, rv, uv; + + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->origin[2] += 16; + AngleVectors (ent->angles, fv, rv, uv); + + VectorMA (dl->origin, 18, fv, dl->origin); + dl->radius = 200 + (rand()&31); + dl->minlight = 32; + dl->die = cl.time + 0.1; + + //johnfitz -- assume muzzle flash accompanied by muzzle flare, which looks bad when lerped + if (r_lerpmodels.value != 2) + { + if (ent == &cl_entities[cl.viewentity]) + cl.viewent.lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + else + ent->lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + } + //johnfitz + } + if (ent->effects & EF_BRIGHTLIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->origin[2] += 16; + dl->radius = 400 + (rand()&31); + dl->die = cl.time + 0.001; + } + if (ent->effects & EF_DIMLIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200 + (rand()&31); + dl->die = cl.time + 0.001; + } + + if (ent->model->flags & EF_GIB) + R_RocketTrail (oldorg, ent->origin, 2); + else if (ent->model->flags & EF_ZOMGIB) + R_RocketTrail (oldorg, ent->origin, 4); + else if (ent->model->flags & EF_TRACER) + R_RocketTrail (oldorg, ent->origin, 3); + else if (ent->model->flags & EF_TRACER2) + R_RocketTrail (oldorg, ent->origin, 5); + else if (ent->model->flags & EF_ROCKET) + { + R_RocketTrail (oldorg, ent->origin, 0); + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200; + dl->die = cl.time + 0.01; + } + else if (ent->model->flags & EF_GRENADE) + R_RocketTrail (oldorg, ent->origin, 1); + else if (ent->model->flags & EF_TRACER3) + R_RocketTrail (oldorg, ent->origin, 6); + + ent->forcelink = false; + + if (i == cl.viewentity && !chase_active.value) + continue; + + if (cl_numvisedicts < MAX_VISEDICTS) + { + cl_visedicts[cl_numvisedicts] = ent; + cl_numvisedicts++; + } + } +} + + +/* +=============== +CL_ReadFromServer + +Read all incoming data from the server +=============== +*/ +int CL_ReadFromServer (void) +{ + int ret; + extern int num_temp_entities; //johnfitz + int num_beams = 0; //johnfitz + int num_dlights = 0; //johnfitz + beam_t *b; //johnfitz + dlight_t *l; //johnfitz + int i; //johnfitz + + + cl.oldtime = cl.time; + cl.time += host_frametime; + + do + { + ret = CL_GetMessage (); + if (ret == -1) + Host_Error ("CL_ReadFromServer: lost server connection"); + if (!ret) + break; + + cl.last_received_message = realtime; + CL_ParseServerMessage (); + } while (ret && cls.state == ca_connected); + + if (cl_shownet.value) + Con_Printf ("\n"); + + CL_RelinkEntities (); + CL_UpdateTEnts (); + +//johnfitz -- devstats + + //visedicts + if (cl_numvisedicts > 256 && dev_peakstats.visedicts <= 256) + Con_DWarning ("%i visedicts exceeds standard limit of 256 (max = %d).\n", cl_numvisedicts, MAX_VISEDICTS); + dev_stats.visedicts = cl_numvisedicts; + dev_peakstats.visedicts = q_max(cl_numvisedicts, dev_peakstats.visedicts); + + //temp entities + if (num_temp_entities > 64 && dev_peakstats.tempents <= 64) + Con_DWarning ("%i tempentities exceeds standard limit of 64 (max = %d).\n", num_temp_entities, MAX_TEMP_ENTITIES); + dev_stats.tempents = num_temp_entities; + dev_peakstats.tempents = q_max(num_temp_entities, dev_peakstats.tempents); + + //beams + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->model && b->endtime >= cl.time) + num_beams++; + if (num_beams > 24 && dev_peakstats.beams <= 24) + Con_DWarning ("%i beams exceeded standard limit of 24 (max = %d).\n", num_beams, MAX_BEAMS); + dev_stats.beams = num_beams; + dev_peakstats.beams = q_max(num_beams, dev_peakstats.beams); + + //dlights + for (i=0, l=cl_dlights ; idie >= cl.time && l->radius) + num_dlights++; + if (num_dlights > 32 && dev_peakstats.dlights <= 32) + Con_DWarning ("%i dlights exceeded standard limit of 32 (max = %d).\n", num_dlights, MAX_DLIGHTS); + dev_stats.dlights = num_dlights; + dev_peakstats.dlights = q_max(num_dlights, dev_peakstats.dlights); + +//johnfitz + +// +// bring the links up to date +// + return 0; +} + +/* +================= +CL_SendCmd +================= +*/ +void CL_SendCmd (void) +{ + usercmd_t cmd; + + if (cls.state != ca_connected) + return; + + if (cls.signon == SIGNONS) + { + // get basic movement from keyboard + CL_BaseMove (&cmd); + + // allow mice or other external controllers to add to the move + IN_Move (&cmd); + + // send the unreliable message + CL_SendMove (&cmd); + } + + if (cls.demoplayback) + { + SZ_Clear (&cls.message); + return; + } + +// send the reliable message + if (!cls.message.cursize) + return; // no message at all + + if (!NET_CanSendMessage (cls.netcon)) + { + Con_DPrintf ("CL_SendCmd: can't send\n"); + return; + } + + if (NET_SendMessage (cls.netcon, &cls.message) == -1) + Host_Error ("CL_SendCmd: lost server connection"); + + SZ_Clear (&cls.message); +} + +/* +============= +CL_Tracepos_f -- johnfitz + +display impact point of trace along VPN +============= +*/ +void CL_Tracepos_f (void) +{ + vec3_t v, w; + + if (cls.state != ca_connected) + return; + + VectorMA(r_refdef.vieworg, 8192.0, vpn, v); + TraceLine(r_refdef.vieworg, v, w); + + if (VectorLength(w) == 0) + Con_Printf ("Tracepos: trace didn't hit anything\n"); + else + Con_Printf ("Tracepos: (%i %i %i)\n", (int)w[0], (int)w[1], (int)w[2]); +} + +/* +============= +CL_Viewpos_f -- johnfitz + +display client's position and angles +============= +*/ +void CL_Viewpos_f (void) +{ + if (cls.state != ca_connected) + return; +#if 0 + //camera position + Con_Printf ("Viewpos: (%i %i %i) %i %i %i\n", + (int)r_refdef.vieworg[0], + (int)r_refdef.vieworg[1], + (int)r_refdef.vieworg[2], + (int)r_refdef.viewangles[PITCH], + (int)r_refdef.viewangles[YAW], + (int)r_refdef.viewangles[ROLL]); +#else + //player position + Con_Printf ("Viewpos: (%i %i %i) %i %i %i\n", + (int)cl_entities[cl.viewentity].origin[0], + (int)cl_entities[cl.viewentity].origin[1], + (int)cl_entities[cl.viewentity].origin[2], + (int)cl.viewangles[PITCH], + (int)cl.viewangles[YAW], + (int)cl.viewangles[ROLL]); +#endif +} + +/* +================= +CL_Init +================= +*/ +void CL_Init (void) +{ + SZ_Alloc (&cls.message, 1024); + + CL_InitInput (); + CL_InitTEnts (); + + Cvar_RegisterVariable (&cl_name); + Cvar_RegisterVariable (&cl_color); + 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 (&lookspring); + Cvar_RegisterVariable (&lookstrafe); + Cvar_RegisterVariable (&sensitivity); + + Cvar_RegisterVariable (&cl_alwaysrun); + + Cvar_RegisterVariable (&m_pitch); + Cvar_RegisterVariable (&m_yaw); + Cvar_RegisterVariable (&m_forward); + Cvar_RegisterVariable (&m_side); + + Cvar_RegisterVariable (&cfg_unbindall); + + Cvar_RegisterVariable (&cl_maxpitch); //johnfitz -- variable pitch clamping + Cvar_RegisterVariable (&cl_minpitch); //johnfitz -- variable pitch clamping + + Cmd_AddCommand ("entities", CL_PrintEntities_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("stop", CL_Stop_f); + Cmd_AddCommand ("playdemo", CL_PlayDemo_f); + Cmd_AddCommand ("timedemo", CL_TimeDemo_f); + + Cmd_AddCommand ("tracepos", CL_Tracepos_f); //johnfitz + Cmd_AddCommand ("viewpos", CL_Viewpos_f); //johnfitz +} + diff --git a/source/cl_parse.c b/source/cl_parse.c new file mode 100644 index 0000000..75bacda --- /dev/null +++ b/source/cl_parse.c @@ -0,0 +1,1246 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "bgmusic.h" + +const char *svc_strings[] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_updatestat", + "svc_version", // [long] 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", // [long] 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", +//johnfitz -- new server messages + "", // 35 + "", // 36 + "svc_skybox", // 37 // [string] skyname + "", // 38 + "", // 39 + "svc_bf", // 40 // no data + "svc_fog", // 41 // [byte] density [byte] red [byte] green [byte] blue [float] time + "svc_spawnbaseline2", //42 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstatic2", // 43 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstaticsound2", // 44 // [coord3] [short] samp [byte] vol [byte] aten + "", // 44 + "", // 45 + "", // 46 + "", // 47 + "", // 48 + "", // 49 +//johnfitz +}; + +qboolean warn_about_nehahra_protocol; //johnfitz + +extern vec3_t v_punchangles[2]; //johnfitz + +//============================================================================= + +/* +=============== +CL_EntityNum + +This error checks and tracks the total number of entities +=============== +*/ +entity_t *CL_EntityNum (int num) +{ + //johnfitz -- check minimum number too + if (num < 0) + Host_Error ("CL_EntityNum: %i is an invalid number",num); + //john + + if (num >= cl.num_entities) + { + if (num >= cl_max_edicts) //johnfitz -- no more MAX_EDICTS + Host_Error ("CL_EntityNum: %i is an invalid number",num); + while (cl.num_entities<=num) + { + cl_entities[cl.num_entities].colormap = vid.colormap; + cl_entities[cl.num_entities].lerpflags |= LERP_RESETMOVE|LERP_RESETANIM; //johnfitz + cl.num_entities++; + } + } + + return &cl_entities[num]; +} + + +/* +================== +CL_ParseStartSoundPacket +================== +*/ +void CL_ParseStartSoundPacket(void) +{ + vec3_t pos; + int channel, ent; + int sound_num; + int volume; + int field_mask; + float attenuation; + int i; + + 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; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (field_mask & SND_LARGEENTITY) + { + ent = (unsigned short) MSG_ReadShort (); + channel = MSG_ReadByte (); + } + else + { + channel = (unsigned short) MSG_ReadShort (); + ent = channel >> 3; + channel &= 7; + } + + if (field_mask & SND_LARGESOUND) + sound_num = (unsigned short) MSG_ReadShort (); + else + sound_num = MSG_ReadByte (); + //johnfitz + + //johnfitz -- check soundnum + if (sound_num >= MAX_SOUNDS) + Host_Error ("CL_ParseStartSoundPacket: %i > MAX_SOUNDS", sound_num); + //johnfitz + + if (ent > cl_max_edicts) //johnfitz -- no more MAX_EDICTS + Host_Error ("CL_ParseStartSoundPacket: ent = %i", ent); + + for (i = 0; i < 3; i++) + pos[i] = MSG_ReadCoord (cl.protocolflags); + + S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation); +} + +/* +================== +CL_KeepaliveMessage + +When the client is taking a long time to load stuff, send keepalive messages +so the server doesn't disconnect. +================== +*/ +static byte net_olddata[NET_MAXMESSAGE]; +void CL_KeepaliveMessage (void) +{ + float time; + static float lastmsg; + int ret; + sizebuf_t old; + byte *olddata; + + if (sv.active) + return; // no need if server is local + if (cls.demoplayback) + return; + +// read messages from server, should just be nops + olddata = net_olddata; + old = net_message; + memcpy (olddata, net_message.data, net_message.cursize); + + do + { + ret = CL_GetMessage (); + switch (ret) + { + default: + Host_Error ("CL_KeepaliveMessage: CL_GetMessage failed"); + case 0: + break; // nothing waiting + case 1: + Host_Error ("CL_KeepaliveMessage: received a message"); + break; + case 2: + if (MSG_ReadByte() != svc_nop) + Host_Error ("CL_KeepaliveMessage: datagram wasn't a nop"); + break; + } + } while (ret); + + net_message = old; + memcpy (net_message.data, olddata, net_message.cursize); + +// check time + time = Sys_DoubleTime (); + if (time - lastmsg < 5) + return; + lastmsg = time; + +// write out a nop + Con_Printf ("--> client to server keepalive\n"); + + MSG_WriteByte (&cls.message, clc_nop); + NET_SendMessage (cls.netcon, &cls.message); + SZ_Clear (&cls.message); +} + +/* +================== +CL_ParseServerInfo +================== +*/ +void CL_ParseServerInfo (void) +{ + const char *str; + int i; + int nummodels, numsounds; + char model_precache[MAX_MODELS][MAX_QPATH]; + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + + Con_DPrintf ("Serverinfo packet received.\n"); + +// ericw -- bring up loading plaque for map changes within a demo. +// it will be hidden in CL_SignonReply. + if (cls.demoplayback) + SCR_BeginLoadingPlaque(); + +// +// wipe the client_state_t struct +// + CL_ClearState (); + +// parse protocol version number + i = MSG_ReadLong (); + //johnfitz -- support multiple protocols + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) { + Con_Printf ("\n"); //because there's no newline after serverinfo print + Host_Error ("Server returned version %i, not %i or %i or %i", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + } + cl.protocol = i; + //johnfitz + + if (cl.protocol == PROTOCOL_RMQ) + { + const unsigned int supportedflags = (PRFL_SHORTANGLE | PRFL_FLOATANGLE | PRFL_24BITCOORD | PRFL_FLOATCOORD | PRFL_EDICTSCALE | PRFL_INT32COORD); + + // mh - read protocol flags from server so that we know what protocol features to expect + cl.protocolflags = (unsigned int) MSG_ReadLong (); + + if (0 != (cl.protocolflags & (~supportedflags))) + { + Con_Warning("PROTOCOL_RMQ protocolflags %i contains unsupported flags\n", cl.protocolflags); + } + } + else cl.protocolflags = 0; + +// parse maxclients + cl.maxclients = MSG_ReadByte (); + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) + { + Host_Error ("Bad maxclients (%u) from server", cl.maxclients); + } + cl.scores = (scoreboard_t *) Hunk_AllocName (cl.maxclients*sizeof(*cl.scores), "scores"); + +// parse gametype + cl.gametype = MSG_ReadByte (); + +// parse signon message + str = MSG_ReadString (); + q_strlcpy (cl.levelname, str, sizeof(cl.levelname)); + +// seperate the printfs so the server message can have a color + Con_Printf ("\n%s\n", Con_Quakebar(40)); //johnfitz + Con_Printf ("%c%s\n", 2, str); + +//johnfitz -- tell user which protocol this is + Con_Printf ("Using protocol %i\n", i); + +// first we go through and touch all of the precache data that still +// happens to be in the cache, so precaching something else doesn't +// needlessly purge it + +// precache models + memset (cl.model_precache, 0, sizeof(cl.model_precache)); + for (nummodels = 1 ; ; nummodels++) + { + str = MSG_ReadString (); + if (!str[0]) + break; + if (nummodels==MAX_MODELS) + { + Host_Error ("Server sent too many model precaches"); + } + q_strlcpy (model_precache[nummodels], str, MAX_QPATH); + Mod_TouchModel (str); + } + + //johnfitz -- check for excessive models + if (nummodels >= 256) + Con_DWarning ("%i models exceeds standard limit of 256 (max = %d).\n", nummodels, MAX_MODELS); + //johnfitz + +// precache sounds + memset (cl.sound_precache, 0, sizeof(cl.sound_precache)); + for (numsounds = 1 ; ; numsounds++) + { + str = MSG_ReadString (); + if (!str[0]) + break; + if (numsounds==MAX_SOUNDS) + { + Host_Error ("Server sent too many sound precaches"); + } + q_strlcpy (sound_precache[numsounds], str, MAX_QPATH); + S_TouchSound (str); + } + + //johnfitz -- check for excessive sounds + if (numsounds >= 256) + Con_DWarning ("%i sounds exceeds standard limit of 256 (max = %d).\n", numsounds, MAX_SOUNDS); + //johnfitz + +// +// now we try to load everything else until a cache allocation fails +// + + // copy the naked name of the map file to the cl structure -- O.S + COM_StripExtension (COM_SkipPath(model_precache[1]), cl.mapname, sizeof(cl.mapname)); + + for (i = 1; i < nummodels; i++) + { + cl.model_precache[i] = Mod_ForName (model_precache[i], false); + if (cl.model_precache[i] == NULL) + { + Host_Error ("Model %s not found", model_precache[i]); + } + CL_KeepaliveMessage (); + } + + S_BeginPrecaching (); + for (i = 1; i < numsounds; i++) + { + cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]); + CL_KeepaliveMessage (); + } + S_EndPrecaching (); + +// local state + cl_entities[0].model = cl.worldmodel = cl.model_precache[1]; + + R_NewMap (); + + //johnfitz -- clear out string; we don't consider identical + //messages to be duplicates if the map has changed in between + con_lastcenterstring[0] = 0; + //johnfitz + + Hunk_Check (); // make sure nothing is hurt + + noclip_anglehack = false; // noclip is turned off at start + + warn_about_nehahra_protocol = true; //johnfitz -- warn about nehahra protocol hack once per server connection + +//johnfitz -- reset developer stats + memset(&dev_stats, 0, sizeof(dev_stats)); + memset(&dev_peakstats, 0, sizeof(dev_peakstats)); + memset(&dev_overflows, 0, sizeof(dev_overflows)); +} + +/* +================== +CL_ParseUpdate + +Parse an entity update message from the server +If an entities model or origin changes from frame to frame, it must be +relinked. Other attributes can change without relinking. +================== +*/ +void CL_ParseUpdate (int bits) +{ + int i; + qmodel_t *model; + int modnum; + qboolean forcelink; + entity_t *ent; + int num; + int skin; + + if (cls.signon == SIGNONS - 1) + { // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + + if (bits & U_MOREBITS) + { + i = MSG_ReadByte (); + bits |= (i<<8); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (cl.protocol == PROTOCOL_FITZQUAKE || cl.protocol == PROTOCOL_RMQ) + { + if (bits & U_EXTEND1) + bits |= MSG_ReadByte() << 16; + if (bits & U_EXTEND2) + bits |= MSG_ReadByte() << 24; + } + //johnfitz + + if (bits & U_LONGENTITY) + num = MSG_ReadShort (); + else + num = MSG_ReadByte (); + + ent = CL_EntityNum (num); + + if (ent->msgtime != cl.mtime[1]) + forcelink = true; // no previous frame to lerp from + else + forcelink = false; + + //johnfitz -- lerping + if (ent->msgtime + 0.2 < cl.mtime[0]) //more than 0.2 seconds since the last message (most entities think every 0.1 sec) + ent->lerpflags |= LERP_RESETANIM; //if we missed a think, we'd be lerping from the wrong frame + //johnfitz + + ent->msgtime = cl.mtime[0]; + + if (bits & U_MODEL) + { + modnum = MSG_ReadByte (); + if (modnum >= MAX_MODELS) + Host_Error ("CL_ParseModel: bad modnum"); + } + else + modnum = ent->baseline.modelindex; + + if (bits & U_FRAME) + ent->frame = MSG_ReadByte (); + else + ent->frame = ent->baseline.frame; + + if (bits & U_COLORMAP) + i = MSG_ReadByte(); + else + i = ent->baseline.colormap; + if (!i) + ent->colormap = vid.colormap; + else + { + if (i > cl.maxclients) + Sys_Error ("i >= cl.maxclients"); + ent->colormap = cl.scores[i-1].translations; + } + if (bits & U_SKIN) + skin = MSG_ReadByte(); + else + skin = ent->baseline.skin; + if (skin != ent->skinnum) + { + ent->skinnum = skin; + if (num > 0 && num <= cl.maxclients) + R_TranslateNewPlayerSkin (num - 1); //johnfitz -- was R_TranslatePlayerSkin + } + if (bits & U_EFFECTS) + ent->effects = MSG_ReadByte(); + else + ent->effects = ent->baseline.effects; + +// shift the known values for interpolation + VectorCopy (ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy (ent->msg_angles[0], ent->msg_angles[1]); + + if (bits & U_ORIGIN1) + ent->msg_origins[0][0] = MSG_ReadCoord (cl.protocolflags); + else + ent->msg_origins[0][0] = ent->baseline.origin[0]; + if (bits & U_ANGLE1) + ent->msg_angles[0][0] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][0] = ent->baseline.angles[0]; + + if (bits & U_ORIGIN2) + ent->msg_origins[0][1] = MSG_ReadCoord (cl.protocolflags); + else + ent->msg_origins[0][1] = ent->baseline.origin[1]; + if (bits & U_ANGLE2) + ent->msg_angles[0][1] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][1] = ent->baseline.angles[1]; + + if (bits & U_ORIGIN3) + ent->msg_origins[0][2] = MSG_ReadCoord (cl.protocolflags); + else + ent->msg_origins[0][2] = ent->baseline.origin[2]; + if (bits & U_ANGLE3) + ent->msg_angles[0][2] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][2] = ent->baseline.angles[2]; + + //johnfitz -- lerping for movetype_step entities + if (bits & U_STEP) + { + ent->lerpflags |= LERP_MOVESTEP; + ent->forcelink = true; + } + else + ent->lerpflags &= ~LERP_MOVESTEP; + //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE and PROTOCOL_NEHAHRA + if (cl.protocol == PROTOCOL_FITZQUAKE || cl.protocol == PROTOCOL_RMQ) + { + if (bits & U_ALPHA) + ent->alpha = MSG_ReadByte(); + else + ent->alpha = ent->baseline.alpha; + if (bits & U_SCALE) + MSG_ReadByte(); // PROTOCOL_RMQ: currently ignored + if (bits & U_FRAME2) + ent->frame = (ent->frame & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_MODEL2) + modnum = (modnum & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_LERPFINISH) + { + ent->lerpfinish = ent->msgtime + ((float)(MSG_ReadByte()) / 255); + ent->lerpflags |= LERP_FINISH; + } + else + ent->lerpflags &= ~LERP_FINISH; + } + else if (cl.protocol == PROTOCOL_NETQUAKE) + { + //HACK: if this bit is set, assume this is PROTOCOL_NEHAHRA + if (bits & U_TRANS) + { + float a, b; + + if (warn_about_nehahra_protocol) + { + Con_Warning ("nonstandard update bit, assuming Nehahra protocol\n"); + warn_about_nehahra_protocol = false; + } + + a = MSG_ReadFloat(); + b = MSG_ReadFloat(); //alpha + if (a == 2) + MSG_ReadFloat(); //fullbright (not using this yet) + ent->alpha = ENTALPHA_ENCODE(b); + } + else + ent->alpha = ent->baseline.alpha; + } + //johnfitz + + //johnfitz -- moved here from above + model = cl.model_precache[modnum]; + if (model != ent->model) + { + ent->model = model; + // automatic animation (torches, etc) can be either all together + // or randomized + if (model) + { + if (model->synctype == ST_RAND) + ent->syncbase = (float)(rand()&0x7fff) / 0x7fff; + else + ent->syncbase = 0.0; + } + else + forcelink = true; // hack to make null model players work + if (num > 0 && num <= cl.maxclients) + R_TranslateNewPlayerSkin (num - 1); //johnfitz -- was R_TranslatePlayerSkin + + ent->lerpflags |= LERP_RESETANIM; //johnfitz -- don't lerp animation across model changes + } + //johnfitz + + if ( forcelink ) + { // didn't have an update last message + VectorCopy (ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy (ent->msg_origins[0], ent->origin); + VectorCopy (ent->msg_angles[0], ent->msg_angles[1]); + VectorCopy (ent->msg_angles[0], ent->angles); + ent->forcelink = true; + } +} + +/* +================== +CL_ParseBaseline +================== +*/ +void CL_ParseBaseline (entity_t *ent, int version) //johnfitz -- added argument +{ + int i; + int bits; //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE + bits = (version == 2) ? MSG_ReadByte() : 0; + ent->baseline.modelindex = (bits & B_LARGEMODEL) ? MSG_ReadShort() : MSG_ReadByte(); + ent->baseline.frame = (bits & B_LARGEFRAME) ? MSG_ReadShort() : MSG_ReadByte(); + //johnfitz + + ent->baseline.colormap = MSG_ReadByte(); + ent->baseline.skin = MSG_ReadByte(); + for (i = 0; i < 3; i++) + { + ent->baseline.origin[i] = MSG_ReadCoord (cl.protocolflags); + ent->baseline.angles[i] = MSG_ReadAngle (cl.protocolflags); + } + + ent->baseline.alpha = (bits & B_ALPHA) ? MSG_ReadByte() : ENTALPHA_DEFAULT; //johnfitz -- PROTOCOL_FITZQUAKE +} + + +/* +================== +CL_ParseClientdata + +Server information pertaining to this client only +================== +*/ +void CL_ParseClientdata (void) +{ + int i, j; + int bits; //johnfitz + + bits = (unsigned short)MSG_ReadShort (); //johnfitz -- read bits here isntead of in CL_ParseServerMessage() + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & SU_EXTEND1) + bits |= (MSG_ReadByte() << 16); + if (bits & SU_EXTEND2) + bits |= (MSG_ReadByte() << 24); + //johnfitz + + if (bits & SU_VIEWHEIGHT) + cl.viewheight = MSG_ReadChar (); + else + cl.viewheight = DEFAULT_VIEWHEIGHT; + + if (bits & SU_IDEALPITCH) + cl.idealpitch = MSG_ReadChar (); + else + cl.idealpitch = 0; + + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + for (i = 0; i < 3; i++) + { + if (bits & (SU_PUNCH1< cl.maxclients) + Sys_Error ("CL_NewTranslation: slot > cl.maxclients"); + dest = cl.scores[slot].translations; + source = vid.colormap; + memcpy (dest, vid.colormap, sizeof(cl.scores[slot].translations)); + top = cl.scores[slot].colors & 0xf0; + bottom = (cl.scores[slot].colors &15)<<4; + R_TranslatePlayerSkin (slot); + + for (i = 0; i < VID_GRADES; i++, dest += 256, source+=256) + { + if (top < 128) // the artists made some backwards ranges. sigh. + memcpy (dest + TOP_RANGE, source + top, 16); + else + { + for (j = 0; j < 16; j++) + dest[TOP_RANGE+j] = source[top+15-j]; + } + + if (bottom < 128) + memcpy (dest + BOTTOM_RANGE, source + bottom, 16); + else + { + for (j = 0; j < 16; j++) + dest[BOTTOM_RANGE+j] = source[bottom+15-j]; + } + } +} + +/* +===================== +CL_ParseStatic +===================== +*/ +void CL_ParseStatic (int version) //johnfitz -- added a parameter +{ + entity_t *ent; + int i; + + i = cl.num_statics; + if (i >= MAX_STATIC_ENTITIES) + Host_Error ("Too many static entities"); + + ent = &cl_static_entities[i]; + cl.num_statics++; + CL_ParseBaseline (ent, version); //johnfitz -- added second parameter + +// copy it to the current state + + ent->model = cl.model_precache[ent->baseline.modelindex]; + ent->lerpflags |= LERP_RESETANIM; //johnfitz -- lerping + ent->frame = ent->baseline.frame; + + ent->colormap = vid.colormap; + ent->skinnum = ent->baseline.skin; + ent->effects = ent->baseline.effects; + ent->alpha = ent->baseline.alpha; //johnfitz -- alpha + + VectorCopy (ent->baseline.origin, ent->origin); + VectorCopy (ent->baseline.angles, ent->angles); + R_AddEfrags (ent); +} + +/* +=================== +CL_ParseStaticSound +=================== +*/ +void CL_ParseStaticSound (int version) //johnfitz -- added argument +{ + vec3_t org; + int sound_num, vol, atten; + int i; + + for (i = 0; i < 3; i++) + org[i] = MSG_ReadCoord (cl.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (version == 2) + sound_num = MSG_ReadShort (); + else + sound_num = MSG_ReadByte (); + //johnfitz + + vol = MSG_ReadByte (); + atten = MSG_ReadByte (); + + S_StaticSound (cl.sound_precache[sound_num], org, vol, atten); +} + + +#define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage (void) +{ + int cmd; + int i; + const char *str; //johnfitz + int total, j, lastcmd; //johnfitz + +// +// if recording demos, copy the message out +// + if (cl_shownet.value == 1) + Con_Printf ("%i ",net_message.cursize); + else if (cl_shownet.value == 2) + Con_Printf ("------------------\n"); + + cl.onground = false; // unless the server says otherwise +// +// parse the message +// + MSG_BeginReading (); + + lastcmd = 0; + while (1) + { + if (msg_badread) + Host_Error ("CL_ParseServerMessage: Bad server message"); + + cmd = MSG_ReadByte (); + + if (cmd == -1) + { + SHOWNET("END OF MESSAGE"); + return; // end of message + } + + // if the high bit of the command byte is set, it is a fast update + if (cmd & U_SIGNAL) //johnfitz -- was 128, changed for clarity + { + SHOWNET("fast update"); + CL_ParseUpdate (cmd&127); + continue; + } + + SHOWNET(svc_strings[cmd]); + + // other commands + switch (cmd) + { + default: + Host_Error ("Illegible server message, previous was %s", svc_strings[lastcmd]); //johnfitz -- added svc_strings[lastcmd] + break; + + case svc_nop: + // Con_Printf ("svc_nop\n"); + break; + + case svc_time: + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = MSG_ReadFloat (); + break; + + case svc_clientdata: + CL_ParseClientdata (); //johnfitz -- removed bits parameter, we will read this inside CL_ParseClientdata() + break; + + case svc_version: + i = MSG_ReadLong (); + //johnfitz -- support multiple protocols + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) + Host_Error ("Server returned version %i, not %i or %i or %i", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + cl.protocol = i; + //johnfitz + break; + + case svc_disconnect: + Host_EndGame ("Server disconnected\n"); + + case svc_print: + Con_Printf ("%s", MSG_ReadString ()); + break; + + case svc_centerprint: + //johnfitz -- log centerprints to console + str = MSG_ReadString (); + SCR_CenterPrint (str); + Con_LogCenterPrint (str); + //johnfitz + break; + + case svc_stufftext: + Cbuf_AddText (MSG_ReadString ()); + break; + + case svc_damage: + V_ParseDamage (); + break; + + case svc_serverinfo: + CL_ParseServerInfo (); + vid.recalc_refdef = true; // leave intermission full screen + break; + + case svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle (cl.protocolflags); + break; + + case svc_setview: + cl.viewentity = MSG_ReadShort (); + break; + + case svc_lightstyle: + i = MSG_ReadByte (); + if (i >= MAX_LIGHTSTYLES) + Sys_Error ("svc_lightstyle > MAX_LIGHTSTYLES"); + q_strlcpy (cl_lightstyle[i].map, MSG_ReadString(), MAX_STYLESTRING); + cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map); + //johnfitz -- save extra info + if (cl_lightstyle[i].length) + { + total = 0; + cl_lightstyle[i].peak = 'a'; + for (j=0; j>3, i&7); + break; + + case svc_updatename: + Sbar_Changed (); + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatename > MAX_SCOREBOARD"); + q_strlcpy (cl.scores[i].name, MSG_ReadString(), MAX_SCOREBOARDNAME); + break; + + case svc_updatefrags: + Sbar_Changed (); + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); + cl.scores[i].frags = MSG_ReadShort (); + break; + + case svc_updatecolors: + Sbar_Changed (); + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD"); + cl.scores[i].colors = MSG_ReadByte (); + CL_NewTranslation (i); + break; + + case svc_particle: + R_ParseParticleEffect (); + break; + + case svc_spawnbaseline: + i = MSG_ReadShort (); + // must use CL_EntityNum() to force cl.num_entities up + CL_ParseBaseline (CL_EntityNum(i), 1); // johnfitz -- added second parameter + break; + + case svc_spawnstatic: + CL_ParseStatic (1); //johnfitz -- added parameter + break; + + case svc_temp_entity: + CL_ParseTEnt (); + break; + + case svc_setpause: + cl.paused = MSG_ReadByte (); + if (cl.paused) + { + CDAudio_Pause (); + BGM_Pause (); + } + else + { + CDAudio_Resume (); + BGM_Resume (); + } + break; + + case svc_signonnum: + i = MSG_ReadByte (); + if (i <= cls.signon) + Host_Error ("Received signon %i when at %i", i, cls.signon); + cls.signon = i; + //johnfitz -- if signonnum==2, signon packet has been fully parsed, so check for excessive static ents and efrags + if (i == 2) + { + if (cl.num_statics > 128) + Con_DWarning ("%i static entities exceeds standard limit of 128 (max = %d).\n", cl.num_statics, MAX_STATIC_ENTITIES); + R_CheckEfrags (); + } + //johnfitz + 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) + Sys_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadLong ();; + break; + + case svc_spawnstaticsound: + CL_ParseStaticSound (1); //johnfitz -- added parameter + break; + + case svc_cdtrack: + cl.cdtrack = MSG_ReadByte (); + cl.looptrack = MSG_ReadByte (); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + BGM_PlayCDtrack ((byte)cls.forcetrack, true); + else + BGM_PlayCDtrack ((byte)cl.cdtrack, true); + break; + + case svc_intermission: + cl.intermission = 1; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + break; + + case svc_finale: + cl.intermission = 2; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + //johnfitz -- log centerprints to console + str = MSG_ReadString (); + SCR_CenterPrint (str); + Con_LogCenterPrint (str); + //johnfitz + break; + + case svc_cutscene: + cl.intermission = 3; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + //johnfitz -- log centerprints to console + str = MSG_ReadString (); + SCR_CenterPrint (str); + Con_LogCenterPrint (str); + //johnfitz + break; + + case svc_sellscreen: + Cmd_ExecuteString ("help", src_command); + break; + + //johnfitz -- new svc types + case svc_skybox: + Sky_LoadSkyBox (MSG_ReadString()); + break; + + case svc_bf: + Cmd_ExecuteString ("bf", src_command); + break; + + case svc_fog: + Fog_ParseServerMessage (); + break; + + case svc_spawnbaseline2: //PROTOCOL_FITZQUAKE + i = MSG_ReadShort (); + // must use CL_EntityNum() to force cl.num_entities up + CL_ParseBaseline (CL_EntityNum(i), 2); + break; + + case svc_spawnstatic2: //PROTOCOL_FITZQUAKE + CL_ParseStatic (2); + break; + + case svc_spawnstaticsound2: //PROTOCOL_FITZQUAKE + CL_ParseStaticSound (2); + break; + //johnfitz + } + + lastcmd = cmd; //johnfitz + } +} + diff --git a/source/cl_tent.c b/source/cl_tent.c new file mode 100644 index 0000000..b1c9b8c --- /dev/null +++ b/source/cl_tent.c @@ -0,0 +1,361 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_tent.c -- client side temporary entities + +#include "quakedef.h" + +int num_temp_entities; +entity_t cl_temp_entities[MAX_TEMP_ENTITIES]; +beam_t cl_beams[MAX_BEAMS]; + +sfx_t *cl_sfx_wizhit; +sfx_t *cl_sfx_knighthit; +sfx_t *cl_sfx_tink1; +sfx_t *cl_sfx_ric1; +sfx_t *cl_sfx_ric2; +sfx_t *cl_sfx_ric3; +sfx_t *cl_sfx_r_exp3; + +/* +================= +CL_ParseTEnt +================= +*/ +void CL_InitTEnts (void) +{ + cl_sfx_wizhit = S_PrecacheSound ("wizard/hit.wav"); + cl_sfx_knighthit = S_PrecacheSound ("hknight/hit.wav"); + cl_sfx_tink1 = S_PrecacheSound ("weapons/tink1.wav"); + cl_sfx_ric1 = S_PrecacheSound ("weapons/ric1.wav"); + cl_sfx_ric2 = S_PrecacheSound ("weapons/ric2.wav"); + cl_sfx_ric3 = S_PrecacheSound ("weapons/ric3.wav"); + cl_sfx_r_exp3 = S_PrecacheSound ("weapons/r_exp3.wav"); +} + +/* +================= +CL_ParseBeam +================= +*/ +void CL_ParseBeam (qmodel_t *m) +{ + int ent; + vec3_t start, end; + beam_t *b; + int i; + + ent = MSG_ReadShort (); + + start[0] = MSG_ReadCoord (cl.protocolflags); + start[1] = MSG_ReadCoord (cl.protocolflags); + start[2] = MSG_ReadCoord (cl.protocolflags); + + end[0] = MSG_ReadCoord (cl.protocolflags); + end[1] = MSG_ReadCoord (cl.protocolflags); + end[2] = MSG_ReadCoord (cl.protocolflags); + +// override any beam with the same entity + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->entity == ent) + { + b->entity = ent; + b->model = m; + b->endtime = cl.time + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + return; + } + +// find a free beam + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { + b->entity = ent; + b->model = m; + b->endtime = cl.time + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + return; + } + } + + //johnfitz -- less spammy overflow message + if (!dev_overflows.beams || dev_overflows.beams + CONSOLE_RESPAM_TIME < realtime ) + { + Con_Printf ("Beam list overflow!\n"); + dev_overflows.beams = realtime; + } + //johnfitz +} + +/* +================= +CL_ParseTEnt +================= +*/ +void CL_ParseTEnt (void) +{ + int type; + vec3_t pos; + dlight_t *dl; + int rnd; + int colorStart, colorLength; + + type = MSG_ReadByte (); + switch (type) + { + case TE_WIZSPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 20, 30); + S_StartSound (-1, 0, cl_sfx_wizhit, pos, 1, 1); + break; + + case TE_KNIGHTSPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 226, 20); + S_StartSound (-1, 0, cl_sfx_knighthit, pos, 1, 1); + break; + + case TE_SPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 10); + 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 + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 20); + + 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_GUNSHOT: // bullet hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 20); + break; + + case TE_EXPLOSION: // rocket explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_ParticleExplosion (pos); + dl = CL_AllocDlight (0); + VectorCopy (pos, dl->origin); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + case TE_TAREXPLOSION: // tarbaby explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_BlobExplosion (pos); + + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + case TE_LIGHTNING1: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt.mdl", true)); + break; + + case TE_LIGHTNING2: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt2.mdl", true)); + break; + + case TE_LIGHTNING3: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt3.mdl", true)); + break; + +// PGM 01/21/97 + case TE_BEAM: // grappling hook beam + CL_ParseBeam (Mod_ForName("progs/beam.mdl", true)); + break; +// PGM 01/21/97 + + case TE_LAVASPLASH: + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_LavaSplash (pos); + break; + + case TE_TELEPORT: + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_TeleportSplash (pos); + break; + + case TE_EXPLOSION2: // color mapped explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + colorStart = MSG_ReadByte (); + colorLength = MSG_ReadByte (); + R_ParticleExplosion2 (pos, colorStart, colorLength); + dl = CL_AllocDlight (0); + VectorCopy (pos, dl->origin); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + default: + Sys_Error ("CL_ParseTEnt: bad type"); + } +} + + +/* +================= +CL_NewTempEntity +================= +*/ +entity_t *CL_NewTempEntity (void) +{ + entity_t *ent; + + if (cl_numvisedicts == MAX_VISEDICTS) + return NULL; + if (num_temp_entities == MAX_TEMP_ENTITIES) + return NULL; + ent = &cl_temp_entities[num_temp_entities]; + memset (ent, 0, sizeof(*ent)); + num_temp_entities++; + cl_visedicts[cl_numvisedicts] = ent; + cl_numvisedicts++; + + ent->colormap = vid.colormap; + return ent; +} + + +/* +================= +CL_UpdateTEnts +================= +*/ +void CL_UpdateTEnts (void) +{ + int i, j; //johnfitz -- use j instead of using i twice, so we don't corrupt memory + beam_t *b; + vec3_t dist, org; + float d; + entity_t *ent; + float yaw, pitch; + float forward; + + num_temp_entities = 0; + + srand ((int) (cl.time * 1000)); //johnfitz -- freeze beams when paused + +// update lightning + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + continue; + + // if coming from the player, update the start position + if (b->entity == cl.viewentity) + { + VectorCopy (cl_entities[cl.viewentity].origin, b->start); + } + + // calculate pitch and yaw + VectorSubtract (b->end, b->start, dist); + + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (int) (atan2(dist[1], dist[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = (int) (atan2(dist[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + // add new entities for the lightning + VectorCopy (b->start, org); + d = VectorNormalize(dist); + while (d > 0) + { + ent = CL_NewTempEntity (); + if (!ent) + return; + VectorCopy (org, ent->origin); + ent->model = b->model; + ent->angles[0] = pitch; + ent->angles[1] = yaw; + ent->angles[2] = rand()%360; + + //johnfitz -- use j instead of using i twice, so we don't corrupt memory + for (j=0 ; j<3 ; j++) + org[j] += dist[j]*30; + d -= 30; + } + } +} diff --git a/source/client.h b/source/client.h new file mode 100644 index 0000000..1c7db18 --- /dev/null +++ b/source/client.h @@ -0,0 +1,376 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _CLIENT_H_ +#define _CLIENT_H_ + +// client.h + +typedef struct +{ + int length; + char map[MAX_STYLESTRING]; + char average; //johnfitz + char peak; //johnfitz +} lightstyle_t; + +typedef struct +{ + char name[MAX_SCOREBOARDNAME]; + float entertime; + int frags; + int colors; // two 4 bit fields + byte translations[VID_GRADES*256]; +} scoreboard_t; + +typedef struct +{ + int destcolor[3]; + int percent; // 0-256 +} cshift_t; + +#define CSHIFT_CONTENTS 0 +#define CSHIFT_DAMAGE 1 +#define CSHIFT_BONUS 2 +#define CSHIFT_POWERUP 3 +#define NUM_CSHIFTS 4 + +#define NAME_LENGTH 64 + + +// +// client_state_t should hold all pieces of the client state +// + +#define SIGNONS 4 // signon messages to receive before connected + +#define MAX_DLIGHTS 64 //johnfitz -- was 32 +typedef struct +{ + vec3_t origin; + float radius; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + vec3_t color; //johnfitz -- lit support via lordhavoc +} dlight_t; + + +#define MAX_BEAMS 32 //johnfitz -- was 24 +typedef struct +{ + int entity; + struct qmodel_s *model; + float endtime; + vec3_t start, end; +} beam_t; + +#define MAX_MAPSTRING 2048 +#define MAX_DEMOS 8 +#define MAX_DEMONAME 16 + +typedef enum { +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; + +// +// the client_static_t structure is persistant through an arbitrary number +// of server connections +// +typedef struct +{ + cactive_t state; + +// personalization data sent to server + char spawnparms[MAX_MAPSTRING]; // to restart a level + +// demo loop control + int demonum; // -1 = don't play demos + char demos[MAX_DEMOS][MAX_DEMONAME]; // when not playing + +// demo recording info must be here, because record is started before +// entering a map (and clearing client_state_t) + qboolean demorecording; + qboolean demoplayback; + +// did the user pause demo playback? (separate from cl.paused because we don't +// want a svc_setpause inside the demo to actually pause demo playback). + qboolean demopaused; + + qboolean timedemo; + int forcetrack; // -1 = use normal cd track + FILE *demofile; + int td_lastframe; // to meter out one message a frame + int td_startframe; // host_framecount at start + float td_starttime; // realtime at second frame of timedemo + +// connection information + int signon; // 0 to SIGNONS + struct qsocket_s *netcon; + sizebuf_t message; // writing buffer to send to server + +} client_static_t; + +extern client_static_t cls; + +// +// the client_state_t structure is wiped completely at every +// server signon +// +typedef struct +{ + int movemessages; // since connecting to this server + // throw out the first couple, so the player + // doesn't accidentally do something the + // first frame + usercmd_t cmd; // last command sent to the server + +// information for local display + int stats[MAX_CL_STATS]; // health, etc + int items; // inventory bit flags + float item_gettime[32]; // cl.time of aquiring item, for blinking + float faceanimtime; // use anim frame if cl.time < this + + cshift_t cshifts[NUM_CSHIFTS]; // color shifts for damage, powerups + cshift_t prev_cshifts[NUM_CSHIFTS]; // and content types + +// 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 temporarliy offset, and an angle reset commands at the start +// of each level and after teleporting. + vec3_t mviewangles[2]; // during demo playback viewangles is lerped + // between these + vec3_t viewangles; + + vec3_t mvelocity[2]; // update by server, used for lean+bob + // (0 is newest) + vec3_t velocity; // lerped between mvelocity[0] and [1] + + vec3_t punchangle; // temporary offset + +// pitch drifting vars + float idealpitch; + float pitchvel; + qboolean nodrift; + float driftmove; + double laststop; + + float viewheight; + float crouch; // local amount for smoothing stepups + + qboolean paused; // send over by server + qboolean onground; + qboolean inwater; + + int intermission; // don't change view angle, full screen, etc + int completed_time; // latched at intermission start + + double mtime[2]; // the timestamp of last two messages + double time; // clients view of time, should be between + // servertime and oldservertime to generate + // a lerp point for other data + double oldtime; // previous cl.time, time-oldtime is used + // to decay light values and smooth step ups + + + float last_received_message; // (realtime) for net trouble icon + +// +// information that is static for the entire time connected to a server +// + struct qmodel_s *model_precache[MAX_MODELS]; + struct sfx_s *sound_precache[MAX_SOUNDS]; + + char mapname[128]; + char levelname[128]; // for display on solo scoreboard //johnfitz -- was 40. + int viewentity; // cl_entitites[cl.viewentity] = player + int maxclients; + int gametype; + +// refresh related state + struct qmodel_s *worldmodel; // cl_entitites[0].model + struct efrag_s *free_efrags; + int num_efrags; + int num_entities; // held in cl_entities array + int num_statics; // held in cl_staticentities array + entity_t viewent; // the gun model + + int cdtrack, looptrack; // cd audio + +// frag scoreboard + scoreboard_t *scores; // [cl.maxclients] + + unsigned protocol; //johnfitz + unsigned protocolflags; +} client_state_t; + + +// +// cvars +// +extern cvar_t cl_name; +extern cvar_t cl_color; + +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_alwaysrun; // QuakeSpasm + +extern cvar_t cl_autofire; + +extern cvar_t cl_shownet; +extern cvar_t cl_nolerp; + +extern cvar_t cfg_unbindall; + +extern cvar_t cl_pitchdriftspeed; +extern cvar_t lookspring; +extern cvar_t lookstrafe; +extern cvar_t sensitivity; + +extern cvar_t m_pitch; +extern cvar_t m_yaw; +extern cvar_t m_forward; +extern cvar_t m_side; + + +#define MAX_TEMP_ENTITIES 256 //johnfitz -- was 64 +#define MAX_STATIC_ENTITIES 4096 //ericw -- was 512 //johnfitz -- was 128 +#define MAX_VISEDICTS 4096 // larger, now we support BSP2 + +extern client_state_t cl; + +// FIXME, allocate dynamically +extern entity_t cl_static_entities[MAX_STATIC_ENTITIES]; +extern lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; +extern dlight_t cl_dlights[MAX_DLIGHTS]; +extern entity_t cl_temp_entities[MAX_TEMP_ENTITIES]; +extern beam_t cl_beams[MAX_BEAMS]; +extern entity_t *cl_visedicts[MAX_VISEDICTS]; +extern int cl_numvisedicts; + +extern entity_t *cl_entities; //johnfitz -- was a static array, now on hunk +extern int cl_max_edicts; //johnfitz -- only changes when new map loads + +//============================================================================= + +// +// cl_main +// +dlight_t *CL_AllocDlight (int key); +void CL_DecayLights (void); + +void CL_Init (void); + +void CL_EstablishConnection (const char *host); +void CL_Signon1 (void); +void CL_Signon2 (void); +void CL_Signon3 (void); +void CL_Signon4 (void); + +void CL_Disconnect (void); +void CL_Disconnect_f (void); +void CL_NextDemo (void); + +// +// cl_input +// +typedef struct +{ + 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_SendCmd (void); +void CL_SendMove (const usercmd_t *cmd); +int CL_ReadFromServer (void); +void CL_BaseMove (usercmd_t *cmd); + +void CL_ParseTEnt (void); +void CL_UpdateTEnts (void); + +void CL_ClearState (void); + +// +// cl_demo.c +// +void CL_StopPlayback (void); +int CL_GetMessage (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_ParseServerMessage (void); +void CL_NewTranslation (int slot); + +// +// view +// +void V_StartPitchDrift (void); +void V_StopPitchDrift (void); + +void V_RenderView (void); +//void V_UpdatePalette (void); //johnfitz +void V_Register (void); +void V_ParseDamage (void); +void V_SetContentsColor (int contents); + +// +// cl_tent +// +void CL_InitTEnts (void); +void CL_SignonReply (void); + +// +// chase +// +extern cvar_t chase_active; + +void Chase_Init (void); +void TraceLine (vec3_t start, vec3_t end, vec3_t impact); +void Chase_UpdateForClient (void); //johnfitz +void Chase_UpdateForDrawing (void); //johnfitz + +#endif /* _CLIENT_H_ */ + diff --git a/source/cmd.c b/source/cmd.c new file mode 100644 index 0000000..783475f --- /dev/null +++ b/source/cmd.c @@ -0,0 +1,862 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +void Cmd_ForwardToServer (void); + +#define MAX_ALIAS_NAME 32 + +#define CMDLINE_LENGTH 256 //johnfitz -- mirrored in common.c + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; + +cmdalias_t *cmd_alias; + +qboolean cmd_wait; + +//============================================================================= + +/* +============ +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" +============ +*/ +void Cmd_Wait_f (void) +{ + cmd_wait = true; +} + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +sizebuf_t cmd_text; + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + SZ_Alloc (&cmd_text, 1<<18); // space for commands and script files. spike -- was 8192, but modern configs can be _HUGE_, at least if they contain lots of comments/docs for things. +} + + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText (const char *text) +{ + int l; + + l = Q_strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Con_Printf ("Cbuf_AddText: overflow\n"); + return; + } + + SZ_Write (&cmd_text, text, Q_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 *) Z_Malloc (templen); + Q_memcpy (temp, cmd_text.data, templen); + SZ_Clear (&cmd_text); + } + else + temp = NULL; // shut up compiler + +// add the entire text of the file + Cbuf_AddText (text); + SZ_Write (&cmd_text, "\n", 1); +// add the copied off data + if (templen) + { + SZ_Write (&cmd_text, temp, templen); + Z_Free (temp); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[1024]; + int quotes; + + while (cmd_text.cursize) + { +// find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = 0; + for (i=0 ; i< cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + if ( !(quotes&1) && text[i] == ';') + break; // don't break if inside a quoted string + if (text[i] == '\n') + break; + } + + if (i > (int)sizeof(line) - 1) + { + memcpy (line, text, sizeof(line) - 1); + line[sizeof(line) - 1] = 0; + } + else + { + memcpy (line, text, i); + line[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 (text, text + i, cmd_text.cursize); + } + +// execute the command line + 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 -- johnfitz -- rewritten to read the "cmdline" cvar, for use with dynamic mod loading + +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 +=============== +*/ +void Cmd_StuffCmds_f (void) +{ + extern cvar_t cmdline; + char cmds[CMDLINE_LENGTH]; + int i, j, plus; + + plus = false; // On Unix, argv[0] is command name + + for (i = 0, j = 0; cmdline.string[i]; i++) + { + if (cmdline.string[i] == '+') + { + plus = true; + if (j > 0) + { + cmds[j-1] = ';'; + cmds[j++] = ' '; + } + } + else if (cmdline.string[i] == '-' && + (i==0 || cmdline.string[i-1] == ' ')) //johnfitz -- allow hypenated map names with +map + plus = false; + else if (plus) + cmds[j++] = cmdline.string[i]; + } + cmds[j] = 0; + + Cbuf_InsertText (cmds); +} + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f (void) +{ + char *f; + int mark; + + if (Cmd_Argc () != 2) + { + Con_Printf ("exec : execute a script file\n"); + return; + } + + mark = Hunk_LowMark (); + f = (char *)COM_LoadHunkFile (Cmd_Argv(1), NULL); + if (!f) + { + Con_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + Con_Printf ("execing %s\n",Cmd_Argv(1)); + + Cbuf_InsertText (f); + Hunk_FreeToLowMark (mark); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; inext, i++) + Con_SafePrintf (" %s: %s", a->name, a->value); + if (i) + Con_SafePrintf ("%i alias command(s)\n", i); + else + Con_SafePrintf ("no alias commands found\n"); + break; + case 2: //output current alias string + for (a = cmd_alias ; a ; a=a->next) + if (!strcmp(Cmd_Argv(1), a->name)) + Con_Printf (" %s: %s", a->name, a->value); + break; + default: //set alias string + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Printf ("Alias name is too long\n"); + return; + } + + // if the alias allready exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + Z_Free (a->value); + break; + } + } + + if (!a) + { + a = (cmdalias_t *) Z_Malloc (sizeof(cmdalias_t)); + a->next = cmd_alias; + cmd_alias = a; + } + strcpy (a->name, s); + + // 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++) + { + q_strlcat (cmd, Cmd_Argv(i), sizeof(cmd)); + if (i != c - 1) + q_strlcat (cmd, " ", sizeof(cmd)); + } + if (q_strlcat(cmd, "\n", sizeof(cmd)) >= sizeof(cmd)) + { + Con_Printf("alias value too long!\n"); + cmd[0] = '\n'; // nullify the string + cmd[1] = 0; + } + + a->value = Z_Strdup (cmd); + break; + } +} + +/* +=============== +Cmd_Unalias_f -- johnfitz +=============== +*/ +void Cmd_Unalias_f (void) +{ + cmdalias_t *a, *prev; + + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("unalias : delete alias\n"); + break; + case 2: + prev = NULL; + for (a = cmd_alias; a; a = a->next) + { + if (!strcmp(Cmd_Argv(1), a->name)) + { + if (prev) + prev->next = a->next; + else + cmd_alias = a->next; + + Z_Free (a->value); + Z_Free (a); + return; + } + prev = a; + } + Con_Printf ("No alias named %s\n", Cmd_Argv(1)); + break; + } +} + +/* +=============== +Cmd_Unaliasall_f -- johnfitz +=============== +*/ +void Cmd_Unaliasall_f (void) +{ + cmdalias_t *blah; + + while (cmd_alias) + { + blah = cmd_alias->next; + Z_Free(cmd_alias->value); + Z_Free(cmd_alias); + cmd_alias = blah; + } +} + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + xcommand_t function; +} cmd_function_t; + + +#define MAX_ARGS 80 + +static int cmd_argc; +static char *cmd_argv[MAX_ARGS]; +static char cmd_null_string[] = ""; +static const char *cmd_args = NULL; + +cmd_source_t cmd_source; + +//johnfitz -- better tab completion +//static cmd_function_t *cmd_functions; // possible commands to execute +cmd_function_t *cmd_functions; // possible commands to execute +//johnfitz + +/* +============ +Cmd_List_f -- johnfitz +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + const char *partial; + int len, count; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = Q_strlen(partial); + } + else + { + partial = NULL; + len = 0; + } + + count=0; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (partial && Q_strncmp (partial,cmd->name, len)) + { + continue; + } + Con_SafePrintf (" %s\n", cmd->name); + count++; + } + + Con_SafePrintf ("%i commands", count); + if (partial) + { + Con_SafePrintf (" beginning with \"%s\"", partial); + } + Con_SafePrintf ("\n"); +} + +static char *Cmd_TintSubstring(const char *in, const char *substr, char *out, size_t outsize) +{ + int l; + char *m; + q_strlcpy(out, in, outsize); + while ((m = q_strcasestr(out, substr))) + { + l = strlen(substr); + while (l-->0) + if (*m >= ' ' && *m < 127) + *m++ |= 0x80; + } + return out; +} + +/* +============ +Cmd_Apropos_f + +scans through each command and cvar names+descriptions for the given substring +we don't support descriptions, so this isn't really all that useful, but even without the sake of consistency it still combines cvars+commands under a single command. +============ +*/ +void Cmd_Apropos_f(void) +{ + char tmpbuf[256]; + int hits = 0; + cmd_function_t *cmd; + cvar_t *var; + const char *substr = Cmd_Argv (1); + if (!*substr) + { + Con_SafePrintf ("%s : search through commands and cvars for the given substring\n", Cmd_Argv(0)); + return; + } + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (q_strcasestr(cmd->name, substr)) + { + hits++; + Con_SafePrintf ("%s\n", Cmd_TintSubstring(cmd->name, substr, tmpbuf, sizeof(tmpbuf))); + } + } + + for (var=Cvar_FindVarAfter("", 0) ; var ; var=var->next) + { + if (q_strcasestr(var->name, substr)) + { + hits++; + Con_SafePrintf ("%s (current value: \"%s\")\n", Cmd_TintSubstring(var->name, substr, tmpbuf, sizeof(tmpbuf)), var->string); + } + } + if (!hits) + Con_SafePrintf ("no cvars nor commands contain that substring\n"); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) +{ + Cmd_AddCommand ("cmdlist", Cmd_List_f); //johnfitz + Cmd_AddCommand ("unalias", Cmd_Unalias_f); //johnfitz + Cmd_AddCommand ("unaliasall", Cmd_Unaliasall_f); //johnfitz + + Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("alias",Cmd_Alias_f); + Cmd_AddCommand ("cmd", Cmd_ForwardToServer); + Cmd_AddCommand ("wait", Cmd_Wait_f); + + Cmd_AddCommand ("apropos", Cmd_Apropos_f); + Cmd_AddCommand ("find", Cmd_Apropos_f); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc (void) +{ + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +const char *Cmd_Argv (int arg) +{ + if (arg < 0 || 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. +============ +*/ +void Cmd_TokenizeString (const char *text) +{ + int i; + +// clear the args from the last string + for (i=0 ; inext) + { + if (!Q_strcmp (cmd_name, cmd->name)) + { + Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + return; + } + } + + cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); + cmd->name = cmd_name; + cmd->function = function; + + //johnfitz -- insert each entry in alphabetical order + if (cmd_functions == NULL || strcmp(cmd->name, cmd_functions->name) < 0) //insert at front + { + cmd->next = cmd_functions; + cmd_functions = cmd; + } + else //insert later + { + prev = cmd_functions; + cursor = cmd_functions->next; + while ((cursor != NULL) && (strcmp(cmd->name, cursor->name) > 0)) + { + prev = cursor; + cursor = cursor->next; + } + cmd->next = prev->next; + prev->next = cmd; + } + //johnfitz +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists (const char *cmd_name) +{ + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!Q_strcmp (cmd_name,cmd->name)) + return true; + } + + return false; +} + + + +/* +============ +Cmd_CompleteCommand +============ +*/ +const char *Cmd_CompleteCommand (const char *partial) +{ + cmd_function_t *cmd; + int len; + + len = Q_strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!Q_strncmp (partial,cmd->name, len)) + return cmd->name; + + return NULL; +} + +/* +============ +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) +{ + cmd_function_t *cmd; + cmdalias_t *a; + + cmd_source = src; + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + return; // no tokens + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!q_strcasecmp (cmd_argv[0],cmd->name)) + { + cmd->function (); + return; + } + } + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!q_strcasecmp (cmd_argv[0], a->name)) + { + Cbuf_InsertText (a->value); + return; + } + } + +// check cvars + if (!Cvar_Command ()) + Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0)); + +} + + +/* +=================== +Cmd_ForwardToServer + +Sends the entire command line over to the server +=================== +*/ +void Cmd_ForwardToServer (void) +{ + if (cls.state != ca_connected) + { + Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0)); + return; + } + + if (cls.demoplayback) + return; // not really connected + + MSG_WriteByte (&cls.message, clc_stringcmd); + if (q_strcasecmp(Cmd_Argv(0), "cmd") != 0) + { + SZ_Print (&cls.message, Cmd_Argv(0)); + SZ_Print (&cls.message, " "); + } + if (Cmd_Argc() > 1) + SZ_Print (&cls.message, Cmd_Args()); + else + SZ_Print (&cls.message, "\n"); +} + + +/* +================ +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) + Sys_Error ("Cmd_CheckParm: NULL"); + + for (i = 1; i < Cmd_Argc (); i++) + if (! q_strcasecmp (parm, Cmd_Argv (i))) + return i; + + return 0; +} + diff --git a/source/cmd.h b/source/cmd.h new file mode 100644 index 0000000..9f688e2 --- /dev/null +++ b/source/cmd.h @@ -0,0 +1,127 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_CMD_H +#define _QUAKE_CMD_H + +// 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 (); + +*/ + +void Cbuf_Init (void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText (const char *text); +// as new commands are generated from the console or keybindings, +// the text is added to the end of the command buffer. + +void Cbuf_InsertText (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_Execute (void); +// Pulls off \n 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. +// Do not call inside a command function! + +//=========================================================================== + +/* + +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_AddCommand (const char *cmd_name, xcommand_t function); +// 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 + +qboolean Cmd_Exists (const char *cmd_name); +// used by the cvar code to check for cvar / command name overlap + +const char *Cmd_CompleteCommand (const char *partial); +// attempts to match a partial command for automatic command line completion +// returns NULL if nothing fits + +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 allways safe. + +int Cmd_CheckParm (const char *parm); +// Returns the position (1 to argc-1) in the command's argument list +// where the given parameter apears, or 0 if not present + +void Cmd_TokenizeString (const char *text); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +void Cmd_ExecuteString (const char *text, cmd_source_t src); +// 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_ForwardToServer (void); +// 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_Print (const char *text); +// used by command functions to send output to either the graphics console or +// passed as a print message to the client + +#endif /* _QUAKE_CMD_H */ + diff --git a/source/common.c b/source/common.c new file mode 100644 index 0000000..a449001 --- /dev/null +++ b/source/common.c @@ -0,0 +1,2463 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "quakedef.h" +#include "q_ctype.h" +#include + +static char *largv[MAX_NUM_ARGVS + 1]; +static char argvdummy[] = " "; + +int safemode; + +cvar_t registered = {"registered","1",CVAR_ROM}; /* set to correct value in COM_CheckRegistered() */ +cvar_t cmdline = {"cmdline","",CVAR_ROM/*|CVAR_SERVERINFO*/}; /* sending cmdline upon CCREQ_RULE_INFO is evil */ + +static qboolean com_modified; // set true if using non-id files + +qboolean fitzmode; + +static void COM_Path_f (void); + +// if a packfile directory differs from this, it is assumed to be hacked +#define PAK0_COUNT 339 /* id1/pak0.pak - v1.0x */ +#define PAK0_CRC_V100 13900 /* id1/pak0.pak - v1.00 */ +#define PAK0_CRC_V101 62751 /* id1/pak0.pak - v1.01 */ +#define PAK0_CRC_V106 32981 /* id1/pak0.pak - v1.06 */ +#define PAK0_CRC (PAK0_CRC_V106) +#define PAK0_COUNT_V091 308 /* id1/pak0.pak - v0.91/0.92, not supported */ +#define PAK0_CRC_V091 28804 /* id1/pak0.pak - v0.91/0.92, not supported */ + +char com_token[1024]; +int com_argc; +char **com_argv; + +#define CMDLINE_LENGTH 256 /* johnfitz -- mirrored in cmd.c */ +char com_cmdline[CMDLINE_LENGTH]; + +qboolean standard_quake = true, rogue, hipnotic; + +// this graphic needs to be in the pak file to use registered features +static unsigned short pop[] = +{ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x6600,0x0000,0x0000,0x0000,0x6600,0x0000, + 0x0000,0x0066,0x0000,0x0000,0x0000,0x0000,0x0067,0x0000, + 0x0000,0x6665,0x0000,0x0000,0x0000,0x0000,0x0065,0x6600, + 0x0063,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6563, + 0x0064,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6564, + 0x0064,0x6564,0x0000,0x6469,0x6969,0x6400,0x0064,0x6564, + 0x0063,0x6568,0x6200,0x0064,0x6864,0x0000,0x6268,0x6563, + 0x0000,0x6567,0x6963,0x0064,0x6764,0x0063,0x6967,0x6500, + 0x0000,0x6266,0x6769,0x6a68,0x6768,0x6a69,0x6766,0x6200, + 0x0000,0x0062,0x6566,0x6666,0x6666,0x6666,0x6562,0x0000, + 0x0000,0x0000,0x0062,0x6364,0x6664,0x6362,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0062,0x6662,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0061,0x6661,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x6500,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x6400,0x0000,0x0000,0x0000 +}; + +/* + +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 precacution +against having a malicious server instruct clients to write files over areas they +shouldn't. + +The "cache directory" is only used during development to save network bandwidth, +especially over ISDN / T1 lines. If there is a cache directory specified, when +a file is found by the normal search path, it will be mirrored into the cache +directory, then opened there. + +FIXME: +The file "parms.txt" will be read out of the game directory and appended to the +current command line arguments to allow different games to initialize startup +parms differently. This could be used to add a "-sspeed 22050" for the high +quality sound edition. Because they are added at the end, they will not +override an explicit setting on the original command line. + +*/ + +//============================================================================ + + +// ClearLink is used for new headnodes +void ClearLink (link_t *l) +{ + l->prev = l->next = l; +} + +void RemoveLink (link_t *l) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +void InsertLinkBefore (link_t *l, link_t *before) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +void InsertLinkAfter (link_t *l, link_t *after) +{ + l->next = after->next; + l->prev = after; + l->prev->next = l; + l->next->prev = l; +} + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int q_strcasecmp(const char * s1, const char * s2) +{ + const char * p1 = s1; + const char * p2 = s2; + char c1, c2; + + if (p1 == p2) + return 0; + + do + { + c1 = q_tolower (*p1++); + c2 = q_tolower (*p2++); + if (c1 == '\0') + break; + } while (c1 == c2); + + return (int)(c1 - c2); +} + +int q_strncasecmp(const char *s1, const char *s2, size_t n) +{ + const char * p1 = s1; + const char * p2 = s2; + char c1, c2; + + if (p1 == p2 || n == 0) + return 0; + + do + { + c1 = q_tolower (*p1++); + c2 = q_tolower (*p2++); + if (c1 == '\0' || c1 != c2) + break; + } while (--n > 0); + + return (int)(c1 - c2); +} + +//spike -- grabbed this from fte, because its useful to me +char *q_strcasestr(const char *haystack, const char *needle) +{ + int c1, c2, c2f; + int i; + c2f = *needle; + if (c2f >= 'a' && c2f <= 'z') + c2f -= ('a' - 'A'); + if (!c2f) + return (char*)haystack; + while (1) + { + c1 = *haystack; + if (!c1) + return NULL; + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c1 == c2f) + { + for (i = 1; ; i++) + { + c1 = haystack[i]; + c2 = needle[i]; + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (!c2) + return (char*)haystack; //end of needle means we found a complete match + if (!c1) //end of haystack means we can't possibly find needle in it any more + return NULL; + if (c1 != c2) //mismatch means no match starting at haystack[0] + break; + } + } + haystack++; + } + return NULL; //didn't find it +} + +char *q_strlwr (char *str) +{ + char *c; + c = str; + while (*c) + { + *c = q_tolower(*c); + c++; + } + return str; +} + +char *q_strupr (char *str) +{ + char *c; + c = str; + while (*c) + { + *c = q_toupper(*c); + c++; + } + return str; +} + +/* platform dependant (v)snprintf function names: */ +#if defined(_WIN32) +#define snprintf_func _snprintf +#define vsnprintf_func _vsnprintf +#else +#define snprintf_func snprintf +#define vsnprintf_func vsnprintf +#endif + +int q_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int ret; + + ret = vsnprintf_func (str, size, format, args); + + if (ret < 0) + ret = (int)size; + if (size == 0) /* no buffer */ + return ret; + if ((size_t)ret >= size) + str[size - 1] = '\0'; + + return ret; +} + +int q_snprintf (char *str, size_t size, const char *format, ...) +{ + int ret; + va_list argptr; + + va_start (argptr, format); + ret = q_vsnprintf (str, size, format, argptr); + va_end (argptr); + + return ret; +} + +void Q_memset (void *dest, int fill, size_t count) +{ + size_t i; + + if ( (((size_t)dest | count) & 3) == 0) + { + count >>= 2; + fill = fill | (fill<<8) | (fill<<16) | (fill<<24); + for (i = 0; i < count; i++) + ((int *)dest)[i] = fill; + } + else + for (i = 0; i < count; i++) + ((byte *)dest)[i] = fill; +} + +void Q_memcpy (void *dest, const void *src, size_t count) +{ + size_t i; + + if (( ( (size_t)dest | (size_t)src | count) & 3) == 0 ) + { + count >>= 2; + for (i = 0; i < count; i++) + ((int *)dest)[i] = ((int *)src)[i]; + } + else + for (i = 0; i < count; i++) + ((byte *)dest)[i] = ((byte *)src)[i]; +} + +int Q_memcmp (const void *m1, const void *m2, size_t count) +{ + while(count) + { + count--; + if (((byte *)m1)[count] != ((byte *)m2)[count]) + return -1; + } + return 0; +} + +void Q_strcpy (char *dest, const char *src) +{ + while (*src) + { + *dest++ = *src++; + } + *dest++ = 0; +} + +void Q_strncpy (char *dest, const char *src, int count) +{ + while (*src && count--) + { + *dest++ = *src++; + } + if (count) + *dest++ = 0; +} + +int Q_strlen (const char *str) +{ + int count; + + count = 0; + while (str[count]) + count++; + + return count; +} + +char *Q_strrchr(const char *s, char c) +{ + int len = Q_strlen(s); + s += len; + while (len--) + { + if (*--s == c) + return (char *)s; + } + return NULL; +} + +void Q_strcat (char *dest, const char *src) +{ + dest += Q_strlen(dest); + Q_strcpy (dest, src); +} + +int Q_strcmp (const char *s1, const char *s2) +{ + while (1) + { + if (*s1 != *s2) + return -1; // strings not equal + if (!*s1) + return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + +int Q_strncmp (const char *s1, const char *s2, int count) +{ + while (1) + { + if (!count--) + return 0; + if (*s1 != *s2) + return -1; // strings not equal + if (!*s1) + return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + +int Q_atoi (const char *str) +{ + int val; + int sign; + int c; + + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val<<4) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val<<4) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val<<4) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + while (1) + { + c = *str++; + if (c <'0' || c > '9') + return val*sign; + val = val*10 + c - '0'; + } + + return 0; +} + + +float Q_atof (const char *str) +{ + double val; + int sign; + int c; + int decimal, total; + + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val*16) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val*16) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val*16) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + decimal = -1; + total = 0; + while (1) + { + c = *str++; + if (c == '.') + { + decimal = total; + continue; + } + if (c <'0' || c > '9') + break; + val = val*10 + c - '0'; + total++; + } + + if (decimal == -1) + return val*sign; + while (total > decimal) + { + val /= 10; + total--; + } + + return val*sign; +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean host_bigendian; + +short (*BigShort) (short l); +short (*LittleShort) (short l); +int (*BigLong) (int l); +int (*LittleLong) (int l); +float (*BigFloat) (float l); +float (*LittleFloat) (float l); + +short ShortSwap (short l) +{ + byte b1, b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1, b2, b3, b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +// +// writing functions +// + +void MSG_WriteChar (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < -128 || c > 127) + Sys_Error ("MSG_WriteChar: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteByte (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < 0 || c > 255) + Sys_Error ("MSG_WriteByte: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteShort (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < ((short)0x8000) || c > (short)0x7fff) + Sys_Error ("MSG_WriteShort: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 2); + buf[0] = c&0xff; + buf[1] = c>>8; +} + +void MSG_WriteLong (sizebuf_t *sb, int c) +{ + byte *buf; + + buf = (byte *) 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, &dat.l, 4); +} + +void MSG_WriteString (sizebuf_t *sb, const char *s) +{ + if (!s) + SZ_Write (sb, "", 1); + else + SZ_Write (sb, s, Q_strlen(s)+1); +} + +//johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096 +void MSG_WriteCoord16 (sizebuf_t *sb, float f) +{ + MSG_WriteShort (sb, Q_rint(f*8)); +} + +//johnfitz -- 16.8 fixed point coords, max range +-32768 +void MSG_WriteCoord24 (sizebuf_t *sb, float f) +{ + MSG_WriteShort (sb, f); + MSG_WriteByte (sb, (int)(f*255)%255); +} + +//johnfitz -- 32-bit float coords +void MSG_WriteCoord32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteCoord (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATCOORD) + MSG_WriteFloat (sb, f); + else if (flags & PRFL_INT32COORD) + MSG_WriteLong (sb, Q_rint (f * 16)); + else if (flags & PRFL_24BITCOORD) + MSG_WriteCoord24 (sb, f); + else MSG_WriteCoord16 (sb, f); +} + +void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + MSG_WriteFloat (sb, f); + else if (flags & PRFL_SHORTANGLE) + MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535); + else MSG_WriteByte (sb, Q_rint(f * 256.0 / 360.0) & 255); //johnfitz -- use Q_rint instead of (int) } +} + +//johnfitz -- for PROTOCOL_FITZQUAKE +void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + MSG_WriteFloat (sb, f); + else MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535); +} +//johnfitz + +// +// reading functions +// +int msg_readcount; +qboolean msg_badread; + +void MSG_BeginReading (void) +{ + msg_readcount = 0; + msg_badread = false; +} + +// returns -1 and sets msg_badread if no more characters are available +int MSG_ReadChar (void) +{ + int c; + + if (msg_readcount+1 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (signed char)net_message.data[msg_readcount]; + msg_readcount++; + + return c; +} + +int MSG_ReadByte (void) +{ + int c; + + if (msg_readcount+1 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (unsigned char)net_message.data[msg_readcount]; + msg_readcount++; + + return c; +} + +int MSG_ReadShort (void) +{ + int c; + + if (msg_readcount+2 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (short)(net_message.data[msg_readcount] + + (net_message.data[msg_readcount+1]<<8)); + + msg_readcount += 2; + + return c; +} + +int MSG_ReadLong (void) +{ + int c; + + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = net_message.data[msg_readcount] + + (net_message.data[msg_readcount+1]<<8) + + (net_message.data[msg_readcount+2]<<16) + + (net_message.data[msg_readcount+3]<<24); + + msg_readcount += 4; + + return c; +} + +float MSG_ReadFloat (void) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = net_message.data[msg_readcount]; + dat.b[1] = net_message.data[msg_readcount+1]; + dat.b[2] = net_message.data[msg_readcount+2]; + dat.b[3] = net_message.data[msg_readcount+3]; + msg_readcount += 4; + + dat.l = LittleLong (dat.l); + + return dat.f; +} + +const char *MSG_ReadString (void) +{ + static char string[2048]; + int c; + size_t l; + + l = 0; + do + { + c = MSG_ReadByte (); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +//johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096 +float MSG_ReadCoord16 (void) +{ + return MSG_ReadShort() * (1.0/8); +} + +//johnfitz -- 16.8 fixed point coords, max range +-32768 +float MSG_ReadCoord24 (void) +{ + return MSG_ReadShort() + MSG_ReadByte() * (1.0/255); +} + +//johnfitz -- 32-bit float coords +float MSG_ReadCoord32f (void) +{ + return MSG_ReadFloat(); +} + +float MSG_ReadCoord (unsigned int flags) +{ + if (flags & PRFL_FLOATCOORD) + return MSG_ReadFloat (); + else if (flags & PRFL_INT32COORD) + return MSG_ReadLong () * (1.0 / 16.0); + else if (flags & PRFL_24BITCOORD) + return MSG_ReadCoord24 (); + else return MSG_ReadCoord16 (); +} + +float MSG_ReadAngle (unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + return MSG_ReadFloat (); + else if (flags & PRFL_SHORTANGLE) + return MSG_ReadShort () * (360.0 / 65536); + else return MSG_ReadChar () * (360.0 / 256); +} + +//johnfitz -- for PROTOCOL_FITZQUAKE +float MSG_ReadAngle16 (unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + return MSG_ReadFloat (); // make sure + else return MSG_ReadShort () * (360.0 / 65536); +} +//johnfitz + +//=========================================================================== + +void SZ_Alloc (sizebuf_t *buf, int startsize) +{ + if (startsize < 256) + startsize = 256; + buf->data = (byte *) Hunk_AllocName (startsize, "sizebuf"); + buf->maxsize = startsize; + buf->cursize = 0; +} + + +void SZ_Free (sizebuf_t *buf) +{ +// Z_Free (buf->data); +// buf->data = NULL; +// buf->maxsize = 0; + buf->cursize = 0; +} + +void SZ_Clear (sizebuf_t *buf) +{ + buf->cursize = 0; +} + +void *SZ_GetSpace (sizebuf_t *buf, int length) +{ + void *data; + + if (buf->cursize + length > buf->maxsize) + { + if (!buf->allowoverflow) + Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); // ericw -- made Host_Error to be less annoying + + if (length > buf->maxsize) + Sys_Error ("SZ_GetSpace: %i is > full buffer size", length); + + buf->overflowed = true; + Con_Printf ("SZ_GetSpace: overflow"); + SZ_Clear (buf); + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +void SZ_Write (sizebuf_t *buf, const void *data, int length) +{ + Q_memcpy (SZ_GetSpace(buf,length),data,length); +} + +void SZ_Print (sizebuf_t *buf, const char *data) +{ + int len = Q_strlen(data) + 1; + + if (buf->data[buf->cursize-1]) + { /* no trailing 0 */ + Q_memcpy ((byte *)SZ_GetSpace(buf, len ) , data, len); + } + else + { /* write over trailing 0 */ + Q_memcpy ((byte *)SZ_GetSpace(buf, len-1)-1, data, len); + } +} + + +//============================================================================ + +/* +============ +COM_SkipPath +============ +*/ +const char *COM_SkipPath (const char *pathname) +{ + const char *last; + + last = pathname; + while (*pathname) + { + if (*pathname == '/') + last = pathname + 1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (const char *in, char *out, size_t outsize) +{ + int length; + + if (!*in) + { + *out = '\0'; + return; + } + if (in != out) /* copy when not in-place editing */ + q_strlcpy (out, in, outsize); + length = (int)strlen(out) - 1; + while (length > 0 && out[length] != '.') + { + --length; + if (out[length] == '/' || out[length] == '\\') + return; /* no extension */ + } + if (length > 0) + out[length] = '\0'; +} + +/* +============ +COM_FileGetExtension - doesn't return NULL +============ +*/ +const char *COM_FileGetExtension (const char *in) +{ + const char *src; + size_t len; + + len = strlen(in); + if (len < 2) /* nothing meaningful */ + return ""; + + src = in + len - 1; + while (src != in && src[-1] != '.') + src--; + if (src == in || strchr(src, '/') != NULL || strchr(src, '\\') != NULL) + return ""; /* no extension, or parent directory has a dot */ + + return src; +} + +/* +============ +COM_ExtractExtension +============ +*/ +void COM_ExtractExtension (const char *in, char *out, size_t outsize) +{ + const char *ext = COM_FileGetExtension (in); + if (! *ext) + *out = '\0'; + else + q_strlcpy (out, ext, outsize); +} + +/* +============ +COM_FileBase +take 'somedir/otherdir/filename.ext', +write only 'filename' to the output +============ +*/ +void COM_FileBase (const char *in, char *out, size_t outsize) +{ + const char *dot, *slash, *s; + + s = in; + slash = in; + dot = NULL; + while (*s) + { + if (*s == '/') + slash = s + 1; + if (*s == '.') + dot = s; + s++; + } + if (dot == NULL) + dot = s; + + if (dot - slash < 2) + q_strlcpy (out, "?model?", outsize); + else + { + size_t len = dot - slash; + if (len >= outsize) + len = outsize - 1; + memcpy (out, slash, len); + out[len] = '\0'; + } +} + +/* +================== +COM_DefaultExtension +if path doesn't have a .EXT, append extension +(extension should include the leading ".") +================== +*/ +#if 0 /* can be dangerous */ +void COM_DefaultExtension (char *path, const char *extension, size_t len) +{ + char *src; + + if (!*path) return; + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + q_strlcat(path, extension, len); +} +#endif + +/* +================== +COM_AddExtension +if path extension doesn't match .EXT, append it +(extension should include the leading ".") +================== +*/ +void COM_AddExtension (char *path, const char *extension, size_t len) +{ + if (strcmp(COM_FileGetExtension(path), extension + 1) != 0) + q_strlcat(path, extension, len); +} + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +const char *COM_Parse (const char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ((c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file + data++; + } + +// skip // comments + if (c == '/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + +// skip /*..*/ comments + if (c == '/' && data[1] == '*') + { + data += 2; + while (*data && !(*data == '*' && data[1] == '/')) + data++; + if (*data) + data += 2; + goto skipwhite; + } + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + if ((c = *data) != 0) + ++data; + if (c == '\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\'' || c == ':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + /* commented out the check for ':' so that ip:port works */ + if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\''/* || c == ':' */) + break; + } while (c > 32); + + com_token[len] = 0; + return data; +} + + +/* +================ +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 < com_argc; i++) + { + if (!com_argv[i]) + continue; // NEXTSTEP sometimes clears appkit vars. + if (!Q_strcmp (parm,com_argv[i])) + return i; + } + + return 0; +} + +/* +================ +COM_CheckRegistered + +Looks for the pop.txt file and verifies it. +Sets the "registered" cvar. +Immediately exits out if an alternate game was attempted to be started without +being registered. +================ +*/ +static void COM_CheckRegistered (void) +{ + int h; + unsigned short check[128]; + int i; + + COM_OpenFile("gfx/pop.lmp", &h, NULL); + + if (h == -1) + { + Cvar_SetROM ("registered", "0"); + Con_Printf ("Playing shareware version.\n"); + if (com_modified) + Sys_Error ("You must have the registered version to use modified games.\n\n" + "Basedir is: %s\n\n" + "Check that this has an " GAMENAME " subdirectory containing pak0.pak and pak1.pak, " + "or use the -basedir command-line option to specify another directory.", + com_basedir); + return; + } + + Sys_FileRead (h, check, sizeof(check)); + COM_CloseFile (h); + + for (i = 0; i < 128; i++) + { + if (pop[i] != (unsigned short)BigShort (check[i])) + Sys_Error ("Corrupted data file."); + } + + for (i = 0; com_cmdline[i]; i++) + { + if (com_cmdline[i]!= ' ') + break; + } + + Cvar_SetROM ("cmdline", &com_cmdline[i]); + Cvar_SetROM ("registered", "1"); + Con_Printf ("Playing registered version.\n"); +} + + +/* +================ +COM_InitArgv +================ +*/ +void COM_InitArgv (int argc, char **argv) +{ + int i, j, n; + +// reconstitute the command line for the cmdline externally visible cvar + n = 0; + + for (j = 0; (j 0 && com_cmdline[n-1] == ' ') + com_cmdline[n-1] = 0; //johnfitz -- kill the trailing space + + Con_Printf("Command line: %s\n", com_cmdline); + + for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc); com_argc++) + { + largv[com_argc] = argv[com_argc]; + if (!Q_strcmp ("-safe", argv[com_argc])) + safemode = 1; + } + + largv[com_argc] = argvdummy; + com_argv = largv; + + if (COM_CheckParm ("-rogue")) + { + rogue = true; + standard_quake = false; + } + + if (COM_CheckParm ("-hipnotic") || COM_CheckParm ("-quoth")) //johnfitz -- "-quoth" support + { + hipnotic = true; + standard_quake = false; + } +} + +/* +================ +Test_f -- johnfitz +================ +*/ +#ifdef _DEBUG +static void FitzTest_f (void) +{ +} +#endif + +/* +================ +COM_Init +================ +*/ +void COM_Init (void) +{ + int i = 0x12345678; + /* U N I X */ + + /* + BE_ORDER: 12 34 56 78 + U N I X + + LE_ORDER: 78 56 34 12 + X I N U + + PDP_ORDER: 34 12 78 56 + N U X I + */ + if ( *(char *)&i == 0x12 ) + host_bigendian = true; + else if ( *(char *)&i == 0x78 ) + host_bigendian = false; + else /* if ( *(char *)&i == 0x34 ) */ + Sys_Error ("Unsupported endianism."); + + if (host_bigendian) + { + BigShort = ShortNoSwap; + LittleShort = ShortSwap; + BigLong = LongNoSwap; + LittleLong = LongSwap; + BigFloat = FloatNoSwap; + LittleFloat = FloatSwap; + } + else /* assumed LITTLE_ENDIAN. */ + { + BigShort = ShortSwap; + LittleShort = ShortNoSwap; + BigLong = LongSwap; + LittleLong = LongNoSwap; + BigFloat = FloatSwap; + LittleFloat = FloatNoSwap; + } + + if (COM_CheckParm("-fitz")) + fitzmode = true; +#ifdef _DEBUG + Cmd_AddCommand ("fitztest", FitzTest_f); //johnfitz +#endif +} + + +/* +============ +va + +does a varargs printf into a temp buffer. cycles between +4 different static buffers. the number of buffers cycled +is defined in VA_NUM_BUFFS. +FIXME: make this buffer size safe someday +============ +*/ +#define VA_NUM_BUFFS 4 +#define VA_BUFFERLEN 1024 + +static char *get_va_buffer(void) +{ + static char va_buffers[VA_NUM_BUFFS][VA_BUFFERLEN]; + static int buffer_idx = 0; + buffer_idx = (buffer_idx + 1) & (VA_NUM_BUFFS - 1); + return va_buffers[buffer_idx]; +} + +char *va (const char *format, ...) +{ + va_list argptr; + char *va_buf; + + va_buf = get_va_buffer (); + va_start (argptr, format); + q_vsnprintf (va_buf, VA_BUFFERLEN, format, argptr); + va_end (argptr); + + return va_buf; +} + +/* +============================================================================= + +QUAKE FILESYSTEM + +============================================================================= +*/ + +int com_filesize; + + +// +// on-disk pakfile +// +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 2048 + +char com_gamedir[MAX_OSPATH]; +char com_basedir[MAX_OSPATH]; +int file_from_pak; // ZOID: global indicating that file came from a pak + +searchpath_t *com_searchpaths; +searchpath_t *com_base_searchpaths; + +/* +============ +COM_Path_f +============ +*/ +static void COM_Path_f (void) +{ + searchpath_t *s; + + Con_Printf ("Current search path:\n"); + for (s = com_searchpaths; s; s = s->next) + { + if (s->pack) + { + Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles); + } + else + Con_Printf ("%s\n", s->filename); + } +} + +/* +============ +COM_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +void COM_WriteFile (const char *filename, const void *data, int len) +{ + int handle; + char name[MAX_OSPATH]; + + Sys_mkdir (com_gamedir); //johnfitz -- if we've switched to a nonexistant gamedir, create it now so we don't crash + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, filename); + + handle = Sys_FileOpenWrite (name); + if (handle == -1) + { + Sys_Printf ("COM_WriteFile: failed on %s\n", name); + return; + } + + Sys_Printf ("COM_WriteFile: %s\n", name); + Sys_FileWrite (handle, data, len); + Sys_FileClose (handle); +} + +/* +============ +COM_CreatePath +============ +*/ +void COM_CreatePath (char *path) +{ + char *ofs; + + for (ofs = path + 1; *ofs; ofs++) + { + if (*ofs == '/') + { // create the directory + *ofs = 0; + Sys_mkdir (path); + *ofs = '/'; + } + } +} + +/* +================ +COM_filelength +================ +*/ +long COM_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +/* +=========== +COM_FindFile + +Finds the file in the search path. +Sets com_filesize and one of handle or file +If neither of file or handle is set, this +can be used for detecting a file's presence. +=========== +*/ +static int COM_FindFile (const char *filename, int *handle, FILE **file, + unsigned int *path_id) +{ + searchpath_t *search; + char netpath[MAX_OSPATH]; + pack_t *pak; + int i, findtime; + + if (file && handle) + Sys_Error ("COM_FindFile: both handle and file set"); + + file_from_pak = 0; + +// +// search through the path, one element at a time +// + for (search = com_searchpaths; search; search = search->next) + { + if (search->pack) /* look through all the pak file elements */ + { + pak = search->pack; + for (i = 0; i < pak->numfiles; i++) + { + if (strcmp(pak->files[i].name, filename) != 0) + continue; + // found it! + com_filesize = pak->files[i].filelen; + file_from_pak = 1; + if (path_id) + *path_id = search->path_id; + if (handle) + { + *handle = pak->handle; + Sys_FileSeek (pak->handle, pak->files[i].filepos); + return com_filesize; + } + else if (file) + { /* open a new file on the pakfile */ + *file = fopen (pak->filename, "rb"); + if (*file) + fseek (*file, pak->files[i].filepos, SEEK_SET); + return com_filesize; + } + else /* for COM_FileExists() */ + { + return com_filesize; + } + } + } + else /* check a file in the directory tree */ + { + if (!registered.value) + { /* if not a registered version, don't ever go beyond base */ + if ( strchr (filename, '/') || strchr (filename,'\\')) + continue; + } + + q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename); + findtime = Sys_FileTime (netpath); + if (findtime == -1) + continue; + + if (path_id) + *path_id = search->path_id; + if (handle) + { + com_filesize = Sys_FileOpenRead (netpath, &i); + *handle = i; + return com_filesize; + } + else if (file) + { + *file = fopen (netpath, "rb"); + com_filesize = (*file == NULL) ? -1 : COM_filelength (*file); + return com_filesize; + } + else + { + return 0; /* dummy valid value for COM_FileExists() */ + } + } + } + + if (strcmp(COM_FileGetExtension(filename), "pcx") != 0 + && strcmp(COM_FileGetExtension(filename), "tga") != 0 + && strcmp(COM_FileGetExtension(filename), "lit") != 0 + && strcmp(COM_FileGetExtension(filename), "ent") != 0) + Con_DPrintf ("FindFile: can't find %s\n", filename); + else Con_DPrintf2("FindFile: can't find %s\n", filename); + // Log pcx, tga, lit, ent misses only if (developer.value >= 2) + + if (handle) + *handle = -1; + if (file) + *file = NULL; + com_filesize = -1; + return com_filesize; +} + + +/* +=========== +COM_FileExists + +Returns whether the file is found in the quake filesystem. +=========== +*/ +qboolean COM_FileExists (const char *filename, unsigned int *path_id) +{ + int ret = COM_FindFile (filename, NULL, NULL, path_id); + return (ret == -1) ? false : true; +} + +/* +=========== +COM_OpenFile + +filename never has a leading slash, but may contain directory walks +returns a handle and a length +it may actually be inside a pak file +=========== +*/ +int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id) +{ + return COM_FindFile (filename, handle, NULL, path_id); +} + +/* +=========== +COM_FOpenFile + +If the requested file is inside a packfile, a new FILE * will be opened +into the file. +=========== +*/ +int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id) +{ + return COM_FindFile (filename, NULL, file, path_id); +} + +/* +============ +COM_CloseFile + +If it is a pak file handle, don't really close it +============ +*/ +void COM_CloseFile (int h) +{ + searchpath_t *s; + + for (s = com_searchpaths; s; s = s->next) + if (s->pack && s->pack->handle == h) + return; + + Sys_FileClose (h); +} + + +/* +============ +COM_LoadFile + +Filename are reletive to the quake directory. +Allways appends a 0 byte. +============ +*/ +#define LOADFILE_ZONE 0 +#define LOADFILE_HUNK 1 +#define LOADFILE_TEMPHUNK 2 +#define LOADFILE_CACHE 3 +#define LOADFILE_STACK 4 +#define LOADFILE_MALLOC 5 + +static byte *loadbuf; +static cache_user_t *loadcache; +static int loadsize; + +byte *COM_LoadFile (const char *path, int usehunk, unsigned int *path_id) +{ + int h; + byte *buf; + char base[32]; + int len; + + buf = NULL; // quiet compiler warning + +// look for it in the filesystem or pack files + len = COM_OpenFile (path, &h, path_id); + if (h == -1) + return NULL; + +// extract the filename base name for hunk tag + COM_FileBase (path, base, sizeof(base)); + + switch (usehunk) + { + case LOADFILE_HUNK: + buf = (byte *) Hunk_AllocName (len+1, base); + break; + case LOADFILE_TEMPHUNK: + buf = (byte *) Hunk_TempAlloc (len+1); + break; + case LOADFILE_ZONE: + buf = (byte *) Z_Malloc (len+1); + break; + case LOADFILE_CACHE: + buf = (byte *) Cache_Alloc (loadcache, len+1, base); + break; + case LOADFILE_STACK: + if (len < loadsize) + buf = loadbuf; + else + buf = (byte *) Hunk_TempAlloc (len+1); + break; + case LOADFILE_MALLOC: + buf = (byte *) malloc (len+1); + break; + default: + Sys_Error ("COM_LoadFile: bad usehunk"); + } + + if (!buf) + Sys_Error ("COM_LoadFile: not enough space for %s", path); + + ((byte *)buf)[len] = 0; + + Sys_FileRead (h, buf, len); + COM_CloseFile (h); + + return buf; +} + +byte *COM_LoadHunkFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_HUNK, path_id); +} + +byte *COM_LoadZoneFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_ZONE, path_id); +} + +byte *COM_LoadTempFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_TEMPHUNK, path_id); +} + +void COM_LoadCacheFile (const char *path, struct cache_user_s *cu, unsigned int *path_id) +{ + loadcache = cu; + COM_LoadFile (path, LOADFILE_CACHE, path_id); +} + +// uses temp hunk if larger than bufsize +byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, unsigned int *path_id) +{ + byte *buf; + + loadbuf = (byte *)buffer; + loadsize = bufsize; + buf = COM_LoadFile (path, LOADFILE_STACK, path_id); + + return buf; +} + +// returns malloc'd memory +byte *COM_LoadMallocFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_MALLOC, path_id); +} + +byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out) +{ + FILE *f; + byte *data; + long len, actuallen; + + // ericw -- this is used by Host_Loadgame_f. Translate CRLF to LF on load games, + // othewise multiline messages have a garbage character at the end of each line. + // TODO: could handle in a way that allows loading CRLF savegames on mac/linux + // without the junk characters appearing. + f = fopen (path, "rt"); + if (f == NULL) + return NULL; + + len = COM_filelength (f); + if (len < 0) + return NULL; + + data = (byte *) malloc (len + 1); + if (data == NULL) + return NULL; + + // (actuallen < len) if CRLF to LF translation was performed + actuallen = fread (data, 1, len, f); + if (ferror(f)) + { + free (data); + return NULL; + } + data[actuallen] = '\0'; + + if (len_out != NULL) + *len_out = actuallen; + return data; +} + +const char *COM_ParseIntNewline(const char *buffer, int *value) +{ + int consumed = 0; + sscanf (buffer, "%i\n%n", value, &consumed); + return buffer + consumed; +} + +const char *COM_ParseFloatNewline(const char *buffer, float *value) +{ + int consumed = 0; + sscanf (buffer, "%f\n%n", value, &consumed); + return buffer + consumed; +} + +const char *COM_ParseStringNewline(const char *buffer) +{ + int consumed = 0; + com_token[0] = '\0'; + sscanf (buffer, "%1023s\n%n", com_token, &consumed); + return buffer + consumed; +} + +/* +================= +COM_LoadPackFile -- johnfitz -- modified based on topaz's tutorial + +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. +================= +*/ +static pack_t *COM_LoadPackFile (const char *packfile) +{ + dpackheader_t header; + int i; + packfile_t *newfiles; + int numpackfiles; + pack_t *pack; + int packhandle; + dpackfile_t info[MAX_FILES_IN_PACK]; + unsigned short crc; + + if (Sys_FileOpenRead (packfile, &packhandle) == -1) + return NULL; + + Sys_FileRead (packhandle, (void *)&header, sizeof(header)); + if (header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K') + Sys_Error ("%s is not a packfile", packfile); + + header.dirofs = LittleLong (header.dirofs); + header.dirlen = LittleLong (header.dirlen); + + numpackfiles = header.dirlen / sizeof(dpackfile_t); + + if (header.dirlen < 0 || header.dirofs < 0) + { + Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)", + packfile, header.dirlen, header.dirofs); + } + if (!numpackfiles) + { + Sys_Printf ("WARNING: %s has no files, ignored\n", packfile); + Sys_FileClose (packhandle); + return NULL; + } + if (numpackfiles > MAX_FILES_IN_PACK) + Sys_Error ("%s has %i files", packfile, numpackfiles); + + if (numpackfiles != PAK0_COUNT) + com_modified = true; // not the original file + + newfiles = (packfile_t *) Z_Malloc(numpackfiles * sizeof(packfile_t)); + + Sys_FileSeek (packhandle, header.dirofs); + Sys_FileRead (packhandle, (void *)info, header.dirlen); + + // crc the directory to check for modifications + CRC_Init (&crc); + for (i = 0; i < header.dirlen; i++) + CRC_ProcessByte (&crc, ((byte *)info)[i]); + if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100) + com_modified = true; + + // parse the directory + for (i = 0; i < numpackfiles; i++) + { + q_strlcpy (newfiles[i].name, info[i].name, sizeof(newfiles[i].name)); + newfiles[i].filepos = LittleLong(info[i].filepos); + newfiles[i].filelen = LittleLong(info[i].filelen); + } + + pack = (pack_t *) Z_Malloc (sizeof (pack_t)); + q_strlcpy (pack->filename, packfile, sizeof(pack->filename)); + pack->handle = packhandle; + pack->numfiles = numpackfiles; + pack->files = newfiles; + + //Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles); + return pack; +} + +/* +================= +COM_AddGameDirectory -- johnfitz -- modified based on topaz's tutorial +================= +*/ +static void COM_AddGameDirectory (const char *base, const char *dir) +{ + int i; + unsigned int path_id; + searchpath_t *search; + pack_t *pak, *qspak; + char pakfile[MAX_OSPATH]; + qboolean been_here = false; + + q_strlcpy (com_gamedir, va("%s/%s", base, dir), sizeof(com_gamedir)); + + // assign a path_id to this game directory + if (com_searchpaths) + path_id = com_searchpaths->path_id << 1; + else path_id = 1U; + +_add_path: + // add the directory to the search path + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + q_strlcpy (search->filename, com_gamedir, sizeof(search->filename)); + search->next = com_searchpaths; + com_searchpaths = search; + + // add any pak files in the format pak0.pak pak1.pak, ... + for (i = 0; ; i++) + { + q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i); + pak = COM_LoadPackFile (pakfile); + if (i != 0 || path_id != 1 || fitzmode) + qspak = NULL; + else { + qboolean old = com_modified; + if (been_here) base = host_parms->userdir; + q_snprintf (pakfile, sizeof(pakfile), "%s/quakespasm.pak", base); + qspak = COM_LoadPackFile (pakfile); + com_modified = old; + } + if (pak) { + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + search->pack = pak; + search->next = com_searchpaths; + com_searchpaths = search; + } + if (qspak) { + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + search->pack = qspak; + search->next = com_searchpaths; + com_searchpaths = search; + } + if (!pak) break; + } + + if (!been_here && host_parms->userdir != host_parms->basedir) + { + been_here = true; + q_strlcpy(com_gamedir, va("%s/%s", host_parms->userdir, dir), sizeof(com_gamedir)); + Sys_mkdir(com_gamedir); + goto _add_path; + } +} + +//============================================================================== +//johnfitz -- dynamic gamedir stuff -- modified by QuakeSpasm team. +//============================================================================== +void ExtraMaps_NewGame (void); +static void COM_Game_f (void) +{ + if (Cmd_Argc() > 1) + { + const char *p = Cmd_Argv(1); + const char *p2 = Cmd_Argv(2); + searchpath_t *search; + + if (!registered.value) //disable shareware quake + { + Con_Printf("You must have the registered version to use modified games\n"); + return; + } + + if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + { + Con_Printf ("gamedir should be a single directory name, not a path\n"); + return; + } + + if (*p2) + { + if (strcmp(p2,"-hipnotic") && strcmp(p2,"-rogue") && strcmp(p2,"-quoth")) { + Con_Printf ("invalid mission pack argument to \"game\"\n"); + return; + } + if (!q_strcasecmp(p, GAMENAME)) { + Con_Printf ("no mission pack arguments to %s game\n", GAMENAME); + return; + } + } + + if (!q_strcasecmp(p, COM_SkipPath(com_gamedir))) //no change + { + if (com_searchpaths->path_id > 1) { //current game not id1 + if (*p2 && com_searchpaths->path_id == 2) { + // rely on QuakeSpasm extension treating '-game missionpack' + // as '-missionpack', otherwise would be a mess + if (!q_strcasecmp(p, &p2[1])) + goto _same; + Con_Printf("reloading game \"%s\" with \"%s\" support\n", p, &p2[1]); + } + else if (!*p2 && com_searchpaths->path_id > 2) + Con_Printf("reloading game \"%s\" without mission pack support\n", p); + else goto _same; + } + else { _same: + Con_Printf("\"game\" is already \"%s\"\n", COM_SkipPath(com_gamedir)); + return; + } + } + + com_modified = true; + + //Kill the server + CL_Disconnect (); + Host_ShutdownServer(true); + + //Write config file + Host_WriteConfiguration (); + + //Kill the extra game if it is loaded + while (com_searchpaths != com_base_searchpaths) + { + if (com_searchpaths->pack) + { + Sys_FileClose (com_searchpaths->pack->handle); + Z_Free (com_searchpaths->pack->files); + Z_Free (com_searchpaths->pack); + } + search = com_searchpaths->next; + Z_Free (com_searchpaths); + com_searchpaths = search; + } + hipnotic = false; + rogue = false; + standard_quake = true; + + if (q_strcasecmp(p, GAMENAME)) //game is not id1 + { + if (*p2) { + COM_AddGameDirectory (com_basedir, &p2[1]); + standard_quake = false; + if (!strcmp(p2,"-hipnotic") || !strcmp(p2,"-quoth")) + hipnotic = true; + else if (!strcmp(p2,"-rogue")) + rogue = true; + if (q_strcasecmp(p, &p2[1])) //don't load twice + COM_AddGameDirectory (com_basedir, p); + } + else { + COM_AddGameDirectory (com_basedir, p); + // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' + if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { + hipnotic = true; + standard_quake = false; + } + else if (!q_strcasecmp(p,"rogue")) { + rogue = true; + standard_quake = false; + } + } + } + else // just update com_gamedir + { + q_snprintf (com_gamedir, sizeof(com_gamedir), "%s/%s", + (host_parms->userdir != host_parms->basedir)? + host_parms->userdir : com_basedir, + GAMENAME); + } + + //clear out and reload appropriate data + Cache_Flush (); + Mod_ResetAll(); + if (!isDedicated) + { + TexMgr_NewGame (); + Draw_NewGame (); + R_NewGame (); + } + ExtraMaps_NewGame (); + DemoList_Rebuild (); + + Con_Printf("\"game\" changed to \"%s\"\n", COM_SkipPath(com_gamedir)); + + VID_Lock (); + Cbuf_AddText ("exec quake.rc\n"); + Cbuf_AddText ("vid_unlock\n"); + } + else //Diplay the current gamedir + Con_Printf("\"game\" is \"%s\"\n", COM_SkipPath(com_gamedir)); +} + +/* +================= +COM_InitFilesystem +================= +*/ +void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial +{ + int i, j; + + Cvar_RegisterVariable (®istered); + Cvar_RegisterVariable (&cmdline); + Cmd_AddCommand ("path", COM_Path_f); + Cmd_AddCommand ("game", COM_Game_f); //johnfitz + + i = COM_CheckParm ("-basedir"); + if (i && i < com_argc-1) + q_strlcpy (com_basedir, com_argv[i + 1], sizeof(com_basedir)); + else + q_strlcpy (com_basedir, host_parms->basedir, sizeof(com_basedir)); + + j = strlen (com_basedir); + if (j < 1) Sys_Error("Bad argument to -basedir"); + if ((com_basedir[j-1] == '\\') || (com_basedir[j-1] == '/')) + com_basedir[j-1] = 0; + + // start up with GAMENAME by default (id1) + COM_AddGameDirectory (com_basedir, GAMENAME); + + /* this is the end of our base searchpath: + * any set gamedirs, such as those from -game command line + * arguments or by the 'game' console command will be freed + * up to here upon a new game command. */ + com_base_searchpaths = com_searchpaths; + + // add mission pack requests (only one should be specified) + if (COM_CheckParm ("-rogue")) + COM_AddGameDirectory (com_basedir, "rogue"); + if (COM_CheckParm ("-hipnotic")) + COM_AddGameDirectory (com_basedir, "hipnotic"); + if (COM_CheckParm ("-quoth")) + COM_AddGameDirectory (com_basedir, "quoth"); + + + i = COM_CheckParm ("-game"); + if (i && i < com_argc-1) + { + const char *p = com_argv[i + 1]; + if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + Sys_Error ("gamedir should be a single directory name, not a path\n"); + com_modified = true; + // don't load mission packs twice + if (COM_CheckParm ("-rogue") && !q_strcasecmp(p, "rogue")) p = NULL; + if (COM_CheckParm ("-hipnotic") && !q_strcasecmp(p, "hipnotic")) p = NULL; + if (COM_CheckParm ("-quoth") && !q_strcasecmp(p, "quoth")) p = NULL; + if (p != NULL) { + COM_AddGameDirectory (com_basedir, p); + // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' + if (!q_strcasecmp(p,"rogue")) { + rogue = true; + standard_quake = false; + } + if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { + hipnotic = true; + standard_quake = false; + } + } + } + + COM_CheckRegistered (); +} + + +/* The following FS_*() stdio replacements are necessary if one is + * to perform non-sequential reads on files reopened on pak files + * because we need the bookkeeping about file start/end positions. + * Allocating and filling in the fshandle_t structure is the users' + * responsibility when the file is initially opened. */ + +size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh) +{ + long byte_size; + long bytes_read; + size_t nmemb_read; + + if (!fh) { + errno = EBADF; + return 0; + } + if (!ptr) { + errno = EFAULT; + return 0; + } + if (!size || !nmemb) { /* no error, just zero bytes wanted */ + errno = 0; + return 0; + } + + byte_size = nmemb * size; + if (byte_size > fh->length - fh->pos) /* just read to end */ + byte_size = fh->length - fh->pos; + bytes_read = fread(ptr, 1, byte_size, fh->file); + fh->pos += bytes_read; + + /* fread() must return the number of elements read, + * not the total number of bytes. */ + nmemb_read = bytes_read / size; + /* even if the last member is only read partially + * it is counted as a whole in the return value. */ + if (bytes_read % size) + nmemb_read++; + + return nmemb_read; +} + +int FS_fseek(fshandle_t *fh, long offset, int whence) +{ +/* I don't care about 64 bit off_t or fseeko() here. + * the quake/hexen2 file system is 32 bits, anyway. */ + int ret; + + if (!fh) { + errno = EBADF; + return -1; + } + + /* the relative file position shouldn't be smaller + * than zero or bigger than the filesize. */ + switch (whence) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += fh->pos; + break; + case SEEK_END: + offset = fh->length + offset; + break; + default: + errno = EINVAL; + return -1; + } + + if (offset < 0) { + errno = EINVAL; + return -1; + } + + if (offset > fh->length) /* just seek to end */ + offset = fh->length; + + ret = fseek(fh->file, fh->start + offset, SEEK_SET); + if (ret < 0) + return ret; + + fh->pos = offset; + return 0; +} + +int FS_fclose(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fclose(fh->file); +} + +long FS_ftell(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fh->pos; +} + +void FS_rewind(fshandle_t *fh) +{ + if (!fh) return; + clearerr(fh->file); + fseek(fh->file, fh->start, SEEK_SET); + fh->pos = 0; +} + +int FS_feof(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + if (fh->pos >= fh->length) + return -1; + return 0; +} + +int FS_ferror(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return ferror(fh->file); +} + +int FS_fgetc(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return EOF; + } + if (fh->pos >= fh->length) + return EOF; + fh->pos += 1; + return fgetc(fh->file); +} + +char *FS_fgets(char *s, int size, fshandle_t *fh) +{ + char *ret; + + if (FS_feof(fh)) + return NULL; + + if (size > (fh->length - fh->pos) + 1) + size = (fh->length - fh->pos) + 1; + + ret = fgets(s, size, fh->file); + fh->pos = ftell(fh->file) - fh->start; + + return ret; +} + +long FS_filelength (fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fh->length; +} + diff --git a/source/common.h b/source/common.h new file mode 100644 index 0000000..eff6b64 --- /dev/null +++ b/source/common.h @@ -0,0 +1,308 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _Q_COMMON_H +#define _Q_COMMON_H + +// comndef.h -- general definitions + +#if defined(_WIN32) +#ifdef _MSC_VER +# pragma warning(disable:4244) + /* 'argument' : conversion from 'type1' to 'type2', + possible loss of data */ +# pragma warning(disable:4305) + /* 'identifier' : truncation from 'type1' to 'type2' */ + /* in our case, truncation from 'double' to 'float' */ +# pragma warning(disable:4267) + /* 'var' : conversion from 'size_t' to 'type', + possible loss of data (/Wp64 warning) */ +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#undef min +#undef max +#define q_min(a, b) (((a) < (b)) ? (a) : (b)) +#define q_max(a, b) (((a) > (b)) ? (a) : (b)) +#define CLAMP(_minval, x, _maxval) \ + ((x) < (_minval) ? (_minval) : \ + (x) > (_maxval) ? (_maxval) : (x)) + +typedef struct sizebuf_s +{ + qboolean allowoverflow; // if false, do a Sys_Error + qboolean overflowed; // set to true if the buffer size failed + byte *data; + int maxsize; + int cursize; +} sizebuf_t; + +void SZ_Alloc (sizebuf_t *buf, int startsize); +void SZ_Free (sizebuf_t *buf); +void SZ_Clear (sizebuf_t *buf); +void *SZ_GetSpace (sizebuf_t *buf, int length); +void SZ_Write (sizebuf_t *buf, const void *data, int length); +void SZ_Print (sizebuf_t *buf, const char *data); // strcats onto the sizebuf + +//============================================================================ + +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + + +void ClearLink (link_t *l); +void RemoveLink (link_t *l); +void InsertLinkBefore (link_t *l, link_t *before); +void InsertLinkAfter (link_t *l, link_t *after); + +// (type *)STRUCT_FROM_LINK(link_t *link, type, member) +// ent = STRUCT_FROM_LINK(link,entity_t,order) +// FIXME: remove this mess! +#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (intptr_t)&(((t *)0)->m))) + +//============================================================================ + +extern qboolean host_bigendian; + +extern short (*BigShort) (short l); +extern short (*LittleShort) (short l); +extern int (*BigLong) (int l); +extern int (*LittleLong) (int l); +extern float (*BigFloat) (float l); +extern float (*LittleFloat) (float l); + +//============================================================================ + +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_WriteCoord (sizebuf_t *sb, float f, unsigned int flags); +void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags); +void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags); //johnfitz + +extern int msg_readcount; +extern qboolean msg_badread; // set if a read goes beyond end of message + +void MSG_BeginReading (void); +int MSG_ReadChar (void); +int MSG_ReadByte (void); +int MSG_ReadShort (void); +int MSG_ReadLong (void); +float MSG_ReadFloat (void); +const char *MSG_ReadString (void); + +float MSG_ReadCoord (unsigned int flags); +float MSG_ReadAngle (unsigned int flags); +float MSG_ReadAngle16 (unsigned int flags); //johnfitz + +//============================================================================ + +void Q_memset (void *dest, int fill, size_t count); +void Q_memcpy (void *dest, const void *src, size_t count); +int Q_memcmp (const void *m1, const void *m2, size_t count); +void Q_strcpy (char *dest, const char *src); +void Q_strncpy (char *dest, const char *src, int count); +int Q_strlen (const char *str); +char *Q_strrchr (const char *s, char c); +void Q_strcat (char *dest, const char *src); +int Q_strcmp (const char *s1, const char *s2); +int Q_strncmp (const char *s1, const char *s2, int count); +int Q_atoi (const char *str); +float Q_atof (const char *str); + + +#include "strl_fn.h" + +/* locale-insensitive strcasecmp replacement functions: */ +extern int q_strcasecmp (const char * s1, const char * s2); +extern int q_strncasecmp (const char *s1, const char *s2, size_t n); + +/* locale-insensitive case-insensitive alternative to strstr */ +extern char *q_strcasestr(const char *haystack, const char *needle); + +/* locale-insensitive strlwr/upr replacement functions: */ +extern char *q_strlwr (char *str); +extern char *q_strupr (char *str); + +/* snprintf, vsnprintf : always use our versions. */ +extern int q_snprintf (char *str, size_t size, const char *format, ...) FUNC_PRINTF(3,4); +extern int q_vsnprintf(char *str, size_t size, const char *format, va_list args) FUNC_PRINTF(3,0); + +//============================================================================ + +extern char com_token[1024]; +extern qboolean com_eof; + +const char *COM_Parse (const char *data); + + +extern int com_argc; +extern char **com_argv; + +extern int safemode; +/* safe mode: in true, the engine will behave as if one + of these arguments were actually on the command line: + -nosound, -nocdaudio, -nomidi, -stdvid, -dibonly, + -nomouse, -nojoy, -nolan + */ + +int COM_CheckParm (const char *parm); + +void COM_Init (void); +void COM_InitArgv (int argc, char **argv); +void COM_InitFilesystem (void); + +const char *COM_SkipPath (const char *pathname); +void COM_StripExtension (const char *in, char *out, size_t outsize); +void COM_FileBase (const char *in, char *out, size_t outsize); +void COM_AddExtension (char *path, const char *extension, size_t len); +#if 0 /* COM_DefaultExtension can be dangerous */ +void COM_DefaultExtension (char *path, const char *extension, size_t len); +#endif +const char *COM_FileGetExtension (const char *in); /* doesn't return NULL */ +void COM_ExtractExtension (const char *in, char *out, size_t outsize); +void COM_CreatePath (char *path); + +char *va (const char *format, ...) FUNC_PRINTF(1,2); +// does a varargs printf into a temp buffer + + +//============================================================================ + +// QUAKEFS +typedef struct +{ + char name[MAX_QPATH]; + int filepos, filelen; +} packfile_t; + +typedef struct pack_s +{ + char filename[MAX_OSPATH]; + int handle; + int numfiles; + packfile_t *files; +} pack_t; + +typedef struct searchpath_s +{ + unsigned int path_id; // identifier assigned to the game directory + // Note that /game1 and + // /game1 have the same id. + char filename[MAX_OSPATH]; + pack_t *pack; // only one of filename / pack will be used + struct searchpath_s *next; +} searchpath_t; + +extern searchpath_t *com_searchpaths; +extern searchpath_t *com_base_searchpaths; + +extern int com_filesize; +struct cache_user_s; + +extern char com_basedir[MAX_OSPATH]; +extern char com_gamedir[MAX_OSPATH]; +extern int file_from_pak; // global indicating that file came from a pak + +void COM_WriteFile (const char *filename, const void *data, int len); +int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id); +int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id); +qboolean COM_FileExists (const char *filename, unsigned int *path_id); +void COM_CloseFile (int h); + +// these procedures open a file using COM_FindFile and loads it into a proper +// buffer. the buffer is allocated with a total size of com_filesize + 1. the +// procedures differ by their buffer allocation method. +byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, + unsigned int *path_id); + // uses the specified stack stack buffer with the specified size + // of bufsize. if bufsize is too short, uses temp hunk. the bufsize + // must include the +1 +byte *COM_LoadTempFile (const char *path, unsigned int *path_id); + // allocates the buffer on the temp hunk. +byte *COM_LoadHunkFile (const char *path, unsigned int *path_id); + // allocates the buffer on the hunk. +byte *COM_LoadZoneFile (const char *path, unsigned int *path_id); + // allocates the buffer on the zone. +void COM_LoadCacheFile (const char *path, struct cache_user_s *cu, + unsigned int *path_id); + // uses cache mem for allocating the buffer. +byte *COM_LoadMallocFile (const char *path, unsigned int *path_id); + // allocates the buffer on the system mem (malloc). + +// Opens the given path directly, ignoring search paths. +// Returns NULL on failure, or else a '\0'-terminated malloc'ed buffer. +// Loads in "t" mode so CRLF to LF translation is performed on Windows. +byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out); + +// Attempts to parse an int, followed by a newline. +// Returns advanced buffer position. +// Doesn't signal parsing failure, but this is not needed for savegame loading. +const char *COM_ParseIntNewline(const char *buffer, int *value); + +// Attempts to parse a float followed by a newline. +// Returns advanced buffer position. +const char *COM_ParseFloatNewline(const char *buffer, float *value); + +// Parse a string of non-whitespace into com_token, then tries to consume a +// newline. Returns advanced buffer position. +const char *COM_ParseStringNewline(const char *buffer); + +/* The following FS_*() stdio replacements are necessary if one is + * to perform non-sequential reads on files reopened on pak files + * because we need the bookkeeping about file start/end positions. + * Allocating and filling in the fshandle_t structure is the users' + * responsibility when the file is initially opened. */ + +typedef struct _fshandle_t +{ + FILE *file; + qboolean pak; /* is the file read from a pak */ + long start; /* file or data start position */ + long length; /* file or data size */ + long pos; /* current position relative to start */ +} fshandle_t; + +size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh); +int FS_fseek(fshandle_t *fh, long offset, int whence); +long FS_ftell(fshandle_t *fh); +void FS_rewind(fshandle_t *fh); +int FS_feof(fshandle_t *fh); +int FS_ferror(fshandle_t *fh); +int FS_fclose(fshandle_t *fh); +int FS_fgetc(fshandle_t *fh); +char *FS_fgets(char *s, int size, fshandle_t *fh); +long FS_filelength (fshandle_t *fh); + + +extern struct cvar_s registered; +extern qboolean standard_quake, rogue, hipnotic; +extern qboolean fitzmode; + /* if true, run in fitzquake mode disabling custom quakespasm hacks */ + +#endif /* _Q_COMMON_H */ + diff --git a/source/console.c b/source/console.c new file mode 100644 index 0000000..5884704 --- /dev/null +++ b/source/console.c @@ -0,0 +1,1308 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// console.c + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#include "quakedef.h" + +int con_linewidth; + +float con_cursorspeed = 4; + +#define CON_TEXTSIZE (1024 * 1024) //ericw -- was 65536. johnfitz -- new default size +#define CON_MINSIZE 16384 //johnfitz -- old default, now the minimum size + +int con_buffersize; //johnfitz -- user can now override default + +qboolean con_forcedup; // because no entities to refresh + +int con_totallines; // total lines in console scrollback +int con_backscroll; // lines up from bottom to display +int con_current; // where next message will be printed +int con_x; // offset in current line for next print +char *con_text = NULL; + +cvar_t con_notifytime = {"con_notifytime","3",CVAR_NONE}; //seconds +cvar_t con_logcenterprint = {"con_logcenterprint", "1", CVAR_NONE}; //johnfitz + +char con_lastcenterstring[1024]; //johnfitz + +#define NUM_CON_TIMES 4 +float con_times[NUM_CON_TIMES]; // realtime time the line was generated + // for transparent notify lines + +int con_vislines; + +qboolean con_debuglog = false; + +qboolean con_initialized; + + +/* +================ +Con_Quakebar -- johnfitz -- returns a bar of the desired length, but never wider than the console + +includes a newline, unless len >= con_linewidth. +================ +*/ +const char *Con_Quakebar (int len) +{ + static char bar[42]; + int i; + + len = q_min(len, (int)sizeof(bar) - 2); + len = q_min(len, con_linewidth); + + bar[0] = '\35'; + for (i = 1; i < len - 1; i++) + bar[i] = '\36'; + bar[len-1] = '\37'; + + if (len < con_linewidth) + { + bar[len] = '\n'; + bar[len+1] = 0; + } + else + bar[len] = 0; + + return bar; +} + +/* +================ +Con_ToggleConsole_f +================ +*/ +extern int history_line; //johnfitz + +void Con_ToggleConsole_f (void) +{ + if (key_dest == key_console/* || (key_dest == key_game && con_forcedup)*/) + { + key_lines[edit_line][1] = 0; // clear any typing + key_linepos = 1; + con_backscroll = 0; //johnfitz -- toggleconsole should return you to the bottom of the scrollback + history_line = edit_line; //johnfitz -- it should also return you to the bottom of the command history + + if (cls.state == ca_connected) + { + IN_Activate(); + key_dest = key_game; + } + else + { + M_Menu_Main_f (); + } + } + else + { + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + } + + SCR_EndLoadingPlaque (); + memset (con_times, 0, sizeof(con_times)); +} + +/* +================ +Con_Clear_f +================ +*/ +static void Con_Clear_f (void) +{ + if (con_text) + Q_memset (con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_backscroll = 0; //johnfitz -- if console is empty, being scrolled up is confusing +} + +/* +================ +Con_Dump_f -- johnfitz -- adapted from quake2 source +================ +*/ +static void Con_Dump_f (void) +{ + int l, x; + const char *line; + FILE *f; + char buffer[1024]; + char name[MAX_OSPATH]; + + q_snprintf (name, sizeof(name), "%s/condump.txt", com_gamedir); + COM_CreatePath (name); + f = fopen (name, "w"); + if (!f) + { + Con_Printf ("ERROR: couldn't open file %s.\n", name); + return; + } + + // skip initial empty lines + for (l = con_current - con_totallines + 1; l <= con_current; l++) + { + line = con_text + (l % con_totallines)*con_linewidth; + for (x = 0; x < con_linewidth; x++) + if (line[x] != ' ') + break; + if (x != con_linewidth) + break; + } + + // write the remaining lines + buffer[con_linewidth] = 0; + for ( ; l <= con_current; l++) + { + line = con_text + (l%con_totallines)*con_linewidth; + strncpy (buffer, line, con_linewidth); + for (x = con_linewidth - 1; x >= 0; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + for (x = 0; buffer[x]; x++) + buffer[x] &= 0x7f; + + fprintf (f, "%s\n", buffer); + } + + fclose (f); + Con_Printf ("Dumped console text to %s.\n", name); +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + int i; + + for (i = 0; i < NUM_CON_TIMES; i++) + con_times[i] = 0; +} + + +/* +================ +Con_MessageMode_f +================ +*/ +static void Con_MessageMode_f (void) +{ + if (cls.state != ca_connected || cls.demoplayback) + return; + chat_team = false; + key_dest = key_message; +} + +/* +================ +Con_MessageMode2_f +================ +*/ +static void Con_MessageMode2_f (void) +{ + if (cls.state != ca_connected || cls.demoplayback) + return; + chat_team = true; + key_dest = key_message; +} + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + char *tbuf; //johnfitz -- tbuf no longer a static array + int mark; //johnfitz + + width = (vid.conwidth >> 3) - 2; //johnfitz -- use vid.conwidth instead of vid.width + + if (width == con_linewidth) + return; + + oldwidth = con_linewidth; + con_linewidth = width; + oldtotallines = con_totallines; + con_totallines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE + numlines = oldtotallines; + + if (con_totallines < numlines) + numlines = con_totallines; + + numchars = oldwidth; + + if (con_linewidth < numchars) + numchars = con_linewidth; + + mark = Hunk_LowMark (); //johnfitz + tbuf = (char *) Hunk_Alloc (con_buffersize); //johnfitz + + Q_memcpy (tbuf, con_text, con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + + for (i = 0; i < numlines; i++) + { + for (j = 0; j < numchars; j++) + { + con_text[(con_totallines - 1 - i) * con_linewidth + j] = + tbuf[((con_current - i + oldtotallines) % oldtotallines) * oldwidth + j]; + } + } + + Hunk_FreeToLowMark (mark); //johnfitz + + Con_ClearNotify (); + + con_backscroll = 0; + con_current = con_totallines - 1; +} + + +/* +================ +Con_Init +================ +*/ +void Con_Init (void) +{ + int i; + + //johnfitz -- user settable console buffer size + i = COM_CheckParm("-consize"); + if (i && i < com_argc-1) + con_buffersize = q_max(CON_MINSIZE,Q_atoi(com_argv[i+1])*1024); + else + con_buffersize = CON_TEXTSIZE; + //johnfitz + + con_text = (char *) Hunk_AllocName (con_buffersize, "context");//johnfitz -- con_buffersize replaces CON_TEXTSIZE + Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_linewidth = -1; + + //johnfitz -- no need to run Con_CheckResize here + con_linewidth = 38; + con_totallines = con_buffersize / con_linewidth;//johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_backscroll = 0; + con_current = con_totallines - 1; + //johnfitz + + Con_Printf ("Console initialized.\n"); + + Cvar_RegisterVariable (&con_notifytime); + Cvar_RegisterVariable (&con_logcenterprint); //johnfitz + + Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f); + Cmd_AddCommand ("messagemode", Con_MessageMode_f); + Cmd_AddCommand ("messagemode2", Con_MessageMode2_f); + Cmd_AddCommand ("clear", Con_Clear_f); + Cmd_AddCommand ("condump", Con_Dump_f); //johnfitz + con_initialized = true; +} + + +/* +=============== +Con_Linefeed +=============== +*/ +static void Con_Linefeed (void) +{ + //johnfitz -- improved scrolling + if (con_backscroll) + con_backscroll++; + if (con_backscroll > con_totallines - (glheight>>3) - 1) + con_backscroll = con_totallines - (glheight>>3) - 1; + //johnfitz + + con_x = 0; + con_current++; + Q_memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth); +} + +/* +================ +Con_Print + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be logged to disk +If no console is visible, the notify window will pop up. +================ +*/ +static void Con_Print (const char *txt) +{ + int y; + int c, l; + static int cr; + int mask; + qboolean boundary; + + //con_backscroll = 0; //johnfitz -- better console scrolling + + if (txt[0] == 1) + { + mask = 128; // go to colored text + S_LocalSound ("misc/talk.wav"); // play talk wav + txt++; + } + else if (txt[0] == 2) + { + mask = 128; // go to colored text + txt++; + } + else + mask = 0; + + boundary = true; + + while ( (c = *txt) ) + { + if (c <= ' ') + { + boundary = true; + } + else if (boundary) + { + // count word length + for (l = 0; l < con_linewidth; l++) + if (txt[l] <= ' ') + break; + + // word wrap + if (l != con_linewidth && (con_x + l > con_linewidth)) + con_x = 0; + + boundary = false; + } + + txt++; + + if (cr) + { + con_current--; + cr = false; + } + + if (!con_x) + { + Con_Linefeed (); + // mark time for transparent overlay + if (con_current >= 0) + con_times[con_current % NUM_CON_TIMES] = realtime; + } + + switch (c) + { + case '\n': + con_x = 0; + break; + + case '\r': + con_x = 0; + cr = 1; + break; + + default: // display character and advance + y = con_current % con_totallines; + con_text[y*con_linewidth+con_x] = c | mask; + con_x++; + if (con_x >= con_linewidth) + con_x = 0; + break; + } + } +} + + +// borrowed from uhexen2 by S.A. for new procs, LOG_Init, LOG_Close + +static char logfilename[MAX_OSPATH]; // current logfile name +static int log_fd = -1; // log file descriptor + +/* +================ +Con_DebugLog +================ +*/ +void Con_DebugLog(const char *msg) +{ + if (log_fd == -1) + return; + + write(log_fd, msg, strlen(msg)); +} + + +/* +================ +Con_Printf + +Handles cursor positioning, line wrapping, etc +================ +*/ +#define MAXPRINTMSG 4096 +void Con_Printf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + static qboolean inupdate; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + +// also echo to debugging console + Sys_Printf ("%s", msg); + +// log all messages to file + if (con_debuglog) + Con_DebugLog(msg); + + if (!con_initialized) + return; + + if (cls.state == ca_dedicated) + return; // no graphics mode + +// write it to the scrollable buffer + Con_Print (msg); + +// update the screen if the console is displayed + if (cls.signon != SIGNONS && !scr_disabled_for_loading ) + { + // protect against infinite loop if something in SCR_UpdateScreen calls + // Con_Printd + if (!inupdate) + { + inupdate = true; + SCR_UpdateScreen (); + inupdate = false; + } + } +} + +/* +================ +Con_DWarning -- ericw + +same as Con_Warning, but only prints if "developer" cvar is set. +use for "exceeds standard limit of" messages, which are only relevant for developers +targetting vanilla engines +================ +*/ +void Con_DWarning (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (!developer.value) + return; // don't confuse non-developers with techie stuff... + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("\x02Warning: "); + Con_Printf ("%s", msg); +} + +/* +================ +Con_Warning -- johnfitz -- prints a warning to the console +================ +*/ +void Con_Warning (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("\x02Warning: "); + Con_Printf ("%s", msg); +} + +/* +================ +Con_DPrintf + +A Con_Printf that only shows up if the "developer" cvar is set +================ +*/ +void Con_DPrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (!developer.value) + return; // don't confuse non-developers with techie stuff... + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("%s", msg); //johnfitz -- was Con_Printf +} + +/* +================ +Con_DPrintf2 -- johnfitz -- only prints if "developer" >= 2 + +currently not used +================ +*/ +void Con_DPrintf2 (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (developer.value >= 2) + { + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + Con_Printf ("%s", msg); + } +} + + +/* +================== +Con_SafePrintf + +Okay to call even when the screen can't be updated +================== +*/ +void Con_SafePrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[1024]; + int temp; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + temp = scr_disabled_for_loading; + scr_disabled_for_loading = true; + Con_Printf ("%s", msg); + scr_disabled_for_loading = temp; +} + +/* +================ +Con_CenterPrintf -- johnfitz -- pad each line with spaces to make it appear centered +================ +*/ +void Con_CenterPrintf (int linewidth, const char *fmt, ...) FUNC_PRINTF(2,3); +void Con_CenterPrintf (int linewidth, const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; //the original message + char line[MAXPRINTMSG]; //one line from the message + char spaces[21]; //buffer for spaces + char *src, *dst; + int len, s; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + linewidth = q_min(linewidth, con_linewidth); + for (src = msg; *src; ) + { + dst = line; + while (*src && *src != '\n') + *dst++ = *src++; + *dst = 0; + if (*src == '\n') + src++; + + len = strlen(line); + if (len < linewidth) + { + s = (linewidth-len)/2; + memset (spaces, ' ', s); + spaces[s] = 0; + Con_Printf ("%s%s\n", spaces, line); + } + else + Con_Printf ("%s\n", line); + } +} + +/* +================== +Con_LogCenterPrint -- johnfitz -- echo centerprint message to the console +================== +*/ +void Con_LogCenterPrint (const char *str) +{ + if (!strcmp(str, con_lastcenterstring)) + return; //ignore duplicates + + if (cl.gametype == GAME_DEATHMATCH && con_logcenterprint.value != 2) + return; //don't log in deathmatch + + strcpy(con_lastcenterstring, str); + + if (con_logcenterprint.value) + { + Con_Printf ("%s", Con_Quakebar(40)); + Con_CenterPrintf (40, "%s\n", str); + Con_Printf ("%s", Con_Quakebar(40)); + Con_ClearNotify (); + } +} + +/* +============================================================================== + + TAB COMPLETION + +============================================================================== +*/ + +//johnfitz -- tab completion stuff +//unique defs +char key_tabpartial[MAXCMDLINE]; +typedef struct tab_s +{ + const char *name; + const char *type; + struct tab_s *next; + struct tab_s *prev; +} tab_t; +tab_t *tablist; + +//defs from elsewhere +extern qboolean keydown[256]; +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + xcommand_t function; +} cmd_function_t; +extern cmd_function_t *cmd_functions; +#define MAX_ALIAS_NAME 32 +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; +extern cmdalias_t *cmd_alias; + +/* +============ +AddToTabList -- johnfitz + +tablist is a doubly-linked loop, alphabetized by name +============ +*/ + +// bash_partial is the string that can be expanded, +// aka Linux Bash shell. -- S.A. +static char bash_partial[80]; +static qboolean bash_singlematch; + +void AddToTabList (const char *name, const char *type) +{ + tab_t *t,*insert; + char *i_bash; + const char *i_name; + + if (!*bash_partial) + { + strncpy (bash_partial, name, 79); + bash_partial[79] = '\0'; + } + else + { + bash_singlematch = 0; + // find max common between bash_partial and name + i_bash = bash_partial; + i_name = name; + while (*i_bash && (*i_bash == *i_name)) + { + i_bash++; + i_name++; + } + *i_bash = 0; + } + + t = (tab_t *) Hunk_Alloc(sizeof(tab_t)); + t->name = name; + t->type = type; + + if (!tablist) //create list + { + tablist = t; + t->next = t; + t->prev = t; + } + else if (strcmp(name, tablist->name) < 0) //insert at front + { + t->next = tablist; + t->prev = tablist->prev; + t->next->prev = t; + t->prev->next = t; + tablist = t; + } + else //insert later + { + insert = tablist; + do + { + if (strcmp(name, insert->name) < 0) + break; + insert = insert->next; + } while (insert != tablist); + + t->next = insert; + t->prev = insert->prev; + t->next->prev = t; + t->prev->next = t; + } +} + +typedef struct arg_completion_type_s +{ + const char *command; + filelist_item_t **filelist; +} arg_completion_type_t; + +static const arg_completion_type_t arg_completion_types[] = +{ + { "map ", &extralevels }, + { "changelevel ", &extralevels }, + { "game ", &modlist }, + { "record ", &demolist }, + { "playdemo ", &demolist }, + { "timedemo ", &demolist } +}; + +static const int num_arg_completion_types = + sizeof(arg_completion_types)/sizeof(arg_completion_types[0]); + +/* +============ +FindCompletion -- stevenaaus +============ +*/ +const char *FindCompletion (const char *partial, filelist_item_t *filelist, int *nummatches_out) +{ + static char matched[32]; + char *i_matched, *i_name; + filelist_item_t *file; + int init, match, plen; + + memset(matched, 0, sizeof(matched)); + plen = strlen(partial); + match = 0; + + for (file = filelist, init = 0; file; file = file->next) + { + if (!strncmp(file->name, partial, plen)) + { + if (init == 0) + { + init = 1; + strncpy (matched, file->name, sizeof(matched)-1); + matched[sizeof(matched)-1] = '\0'; + } + else + { // find max common + i_matched = matched; + i_name = file->name; + while (*i_matched && (*i_matched == *i_name)) + { + i_matched++; + i_name++; + } + *i_matched = 0; + } + match++; + } + } + + *nummatches_out = match; + + if (match > 1) + { + for (file = filelist; file; file = file->next) + { + if (!strncmp(file->name, partial, plen)) + Con_SafePrintf (" %s\n", file->name); + } + Con_SafePrintf ("\n"); + } + + return matched; +} + +/* +============ +BuildTabList -- johnfitz +============ +*/ +void BuildTabList (const char *partial) +{ + cmdalias_t *alias; + cvar_t *cvar; + cmd_function_t *cmd; + int len; + + tablist = NULL; + len = strlen(partial); + + bash_partial[0] = 0; + bash_singlematch = 1; + + cvar = Cvar_FindVarAfter ("", CVAR_NONE); + for ( ; cvar ; cvar=cvar->next) + if (!Q_strncmp (partial, cvar->name, len)) + AddToTabList (cvar->name, "cvar"); + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!Q_strncmp (partial,cmd->name, len)) + AddToTabList (cmd->name, "command"); + + for (alias=cmd_alias ; alias ; alias=alias->next) + if (!Q_strncmp (partial, alias->name, len)) + AddToTabList (alias->name, "alias"); +} + +/* +============ +Con_TabComplete -- johnfitz +============ +*/ +void Con_TabComplete (void) +{ + char partial[MAXCMDLINE]; + const char *match; + static char *c; + tab_t *t; + int mark, i, j; + +// if editline is empty, return + if (key_lines[edit_line][1] == 0) + return; + +// get partial string (space -> cursor) + if (!key_tabpartial[0]) //first time through, find new insert point. (Otherwise, use previous.) + { + //work back from cursor until you find a space, quote, semicolon, or prompt + c = key_lines[edit_line] + key_linepos - 1; //start one space left of cursor + while (*c!=' ' && *c!='\"' && *c!=';' && c!=key_lines[edit_line]) + c--; + c++; //start 1 char after the separator we just found + } + for (i = 0; c + i < key_lines[edit_line] + key_linepos; i++) + partial[i] = c[i]; + partial[i] = 0; + +// Map autocomplete function -- S.A +// Since we don't have argument completion, this hack will do for now... + for (j=0; j= MAXCMDLINE) + key_linepos = MAXCMDLINE - 1; + // if only one match, append a space + if (key_linepos < MAXCMDLINE - 1 && + key_lines[edit_line][key_linepos] == 0 && (nummatches == 1)) + { + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + } + c = key_lines[edit_line] + key_linepos; + return; + } + } + +//if partial is empty, return + if (partial[0] == 0) + return; + +//trim trailing space becuase it screws up string comparisons + if (i > 0 && partial[i-1] == ' ') + partial[i-1] = 0; + +// find a match + mark = Hunk_LowMark(); + if (!key_tabpartial[0]) //first time through + { + q_strlcpy (key_tabpartial, partial, MAXCMDLINE); + BuildTabList (key_tabpartial); + + if (!tablist) + return; + + // print list if length > 1 + if (tablist->next != tablist) + { + t = tablist; + Con_SafePrintf("\n"); + do + { + Con_SafePrintf(" %s (%s)\n", t->name, t->type); + t = t->next; + } while (t != tablist); + Con_SafePrintf("\n"); + } + + // match = tablist->name; + // First time, just show maximum matching chars -- S.A. + match = bash_partial; + } + else + { + BuildTabList (key_tabpartial); + + if (!tablist) + return; + + //find current match -- can't save a pointer because the list will be rebuilt each time + t = tablist; + match = keydown[K_SHIFT] ? t->prev->name : t->name; + do + { + if (!Q_strcmp(t->name, partial)) + { + match = keydown[K_SHIFT] ? t->prev->name : t->next->name; + break; + } + t = t->next; + } while (t != tablist); + } + Hunk_FreeToLowMark(mark); //it's okay to free it here because match is a pointer to persistent data + +// insert new match into edit line + q_strlcpy (partial, match, MAXCMDLINE); //first copy match string + q_strlcat (partial, key_lines[edit_line] + key_linepos, MAXCMDLINE); //then add chars after cursor + *c = '\0'; //now copy all of this into edit line + q_strlcat (key_lines[edit_line], partial, MAXCMDLINE); + key_linepos = c - key_lines[edit_line] + Q_strlen(match); //set new cursor position + if (key_linepos >= MAXCMDLINE) + key_linepos = MAXCMDLINE - 1; + +// if cursor is at end of string, let's append a space to make life easier + if (key_linepos < MAXCMDLINE - 1 && + key_lines[edit_line][key_linepos] == 0 && bash_singlematch) + { + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + // S.A.: the map argument completion (may be in combination with the bash-style + // display behavior changes, causes weirdness when completing the arguments for + // the changelevel command. the line below "fixes" it, although I'm not sure about + // the reason, yet, neither do I know any possible side effects of it: + c = key_lines[edit_line] + key_linepos; + } +} + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + int i, x, v; + const char *text; + float time; + + GL_SetCanvas (CANVAS_CONSOLE); //johnfitz + v = vid.conheight; //johnfitz + + for (i = con_current-NUM_CON_TIMES+1; i <= con_current; i++) + { + if (i < 0) + continue; + time = con_times[i % NUM_CON_TIMES]; + if (time == 0) + continue; + time = realtime - time; + if (time > con_notifytime.value) + continue; + text = con_text + (i % con_totallines)*con_linewidth; + + clearnotify = 0; + + for (x = 0; x < con_linewidth; x++) + Draw_Character ((x+1)<<3, v, text[x]); + + v += 8; + + scr_tileclear_updates = 0; //johnfitz + } + + if (key_dest == key_message) + { + clearnotify = 0; + + if (chat_team) + { + Draw_String (8, v, "say_team:"); + x = 11; + } + else + { + Draw_String (8, v, "say:"); + x = 6; + } + + text = Key_GetChatBuffer(); + i = Key_GetChatMsgLen(); + if (i > con_linewidth - x - 1) + text += i - con_linewidth + x + 1; + + while (*text) + { + Draw_Character (x<<3, v, *text); + x++; + text++; + } + + Draw_Character (x<<3, v, 10 + ((int)(realtime*con_cursorspeed)&1)); + v += 8; + + scr_tileclear_updates = 0; //johnfitz + } +} + +/* +================ +Con_DrawInput -- johnfitz -- modified to allow insert editing + +The input line scrolls horizontally if typing goes beyond the right edge +================ +*/ +extern qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling + +void Con_DrawInput (void) +{ + int i, ofs; + + if (key_dest != key_console && !con_forcedup) + return; // don't draw anything + +// prestep if horizontally scrolling + if (key_linepos >= con_linewidth) + ofs = 1 + key_linepos - con_linewidth; + else + ofs = 0; + +// draw input string + for (i = 0; key_lines[edit_line][i+ofs] && i < con_linewidth; i++) + Draw_Character ((i+1)<<3, vid.conheight - 16, key_lines[edit_line][i+ofs]); + +// johnfitz -- new cursor handling + if (!((int)((realtime-key_blinktime)*con_cursorspeed) & 1)) + { + i = key_linepos - ofs; + Draw_Pic ((i+1)<<3, vid.conheight - 16, key_insert ? pic_ins : pic_ovr); + } +} + +/* +================ +Con_DrawConsole -- johnfitz -- heavy revision + +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, qboolean drawinput) +{ + int i, x, y, j, sb, rows; + const char *text; + char ver[32]; + + if (lines <= 0) + return; + + con_vislines = lines * vid.conheight / glheight; + GL_SetCanvas (CANVAS_CONSOLE); + +// draw the background + Draw_ConsoleBackground (); + +// draw the buffer text + rows = (con_vislines +7)/8; + y = vid.conheight - rows*8; + rows -= 2; //for input and version lines + sb = (con_backscroll) ? 2 : 0; + + for (i = con_current - rows + 1; i <= con_current - sb; i++, y += 8) + { + j = i - con_backscroll; + if (j < 0) + j = 0; + text = con_text + (j % con_totallines)*con_linewidth; + + for (x = 0; x < con_linewidth; x++) + Draw_Character ( (x + 1)<<3, y, text[x]); + } + +// draw scrollback arrows + if (con_backscroll) + { + y += 8; // blank line + for (x = 0; x < con_linewidth; x += 4) + Draw_Character ((x + 1)<<3, y, '^'); + y += 8; + } + +// draw the input prompt, user text, and cursor + if (drawinput) + Con_DrawInput (); + +//draw version number in bottom right + y += 8; + q_snprintf (ver, sizeof(ver), "QuakeSpasm " QUAKESPASM_VER_STRING); + for (x = 0; x < (int)strlen(ver); x++) + Draw_Character ((con_linewidth - strlen(ver) + x + 2)<<3, y, ver[x] /*+ 128*/); +} + + +/* +================== +Con_NotifyBox +================== +*/ +void Con_NotifyBox (const char *text) +{ + double t1, t2; + int lastkey, lastchar; + +// during startup for sound / cd warnings + Con_Printf ("\n\n%s", Con_Quakebar(40)); //johnfitz + Con_Printf ("%s", text); + Con_Printf ("Press a key.\n"); + Con_Printf ("%s", Con_Quakebar(40)); //johnfitz + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + + Key_BeginInputGrab (); + do + { + t1 = Sys_DoubleTime (); + SCR_UpdateScreen (); + Sys_SendKeyEvents (); + Key_GetGrabbedInput (&lastkey, &lastchar); + Sys_Sleep (16); + t2 = Sys_DoubleTime (); + realtime += t2-t1; // make the cursor blink + } while (lastkey == -1 && lastchar == -1); + Key_EndInputGrab (); + + Con_Printf ("\n"); + IN_Activate(); + key_dest = key_game; + realtime = 0; // put the cursor back to invisible +} + + +void LOG_Init (quakeparms_t *parms) +{ + time_t inittime; + char session[24]; + + if (!COM_CheckParm("-condebug")) + return; + + inittime = time (NULL); + strftime (session, sizeof(session), "%m/%d/%Y %H:%M:%S", localtime(&inittime)); + q_snprintf (logfilename, sizeof(logfilename), "%s/qconsole.log", parms->basedir); + +// unlink (logfilename); + + log_fd = open (logfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (log_fd == -1) + { + fprintf (stderr, "Error: Unable to create log file %s\n", logfilename); + return; + } + + con_debuglog = true; + Con_DebugLog (va("LOG started on: %s \n", session)); + +} + +void LOG_Close (void) +{ + if (log_fd == -1) + return; + close (log_fd); + log_fd = -1; +} + diff --git a/source/console.h b/source/console.h new file mode 100644 index 0000000..0fa4a8c --- /dev/null +++ b/source/console.h @@ -0,0 +1,69 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_forcedup; // because no entities to refresh +extern qboolean con_initialized; +extern byte *con_chars; + +extern char con_lastcenterstring[]; //johnfitz + +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_DrawConsole (int lines, qboolean drawinput); +void Con_Printf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DWarning (const char *fmt, ...) FUNC_PRINTF(1,2); //ericw +void Con_Warning (const char *fmt, ...) FUNC_PRINTF(1,2); //johnfitz +void Con_DPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DPrintf2 (const char *fmt, ...) FUNC_PRINTF(1,2); //johnfitz +void Con_SafePrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_ToggleConsole_f (void); + +void Con_NotifyBox (const char *text); // during startup for sound / cd warnings + +void Con_Show (void); +void Con_Hide (void); + +const char *Con_Quakebar (int len); +void Con_TabComplete (void); +void Con_LogCenterPrint (const char *str); + +// +// debuglog +// +void LOG_Init (quakeparms_t *parms); +void LOG_Close (void); +void Con_DebugLog (const char *msg); + +#endif /* __CONSOLE_H */ + diff --git a/source/crc.c b/source/crc.c new file mode 100644 index 0000000..37696d5 --- /dev/null +++ b/source/crc.c @@ -0,0 +1,94 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +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. + +*/ +/* crc.c */ + +#include "quakedef.h" +#include "crc.h" + +// 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 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} + +//johnfitz -- texture crc +unsigned short CRC_Block (const byte *start, int count) +{ + unsigned short crc; + + CRC_Init (&crc); + while (count--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ *start++]; + + return crc; +} diff --git a/source/crc.h b/source/crc.h new file mode 100644 index 0000000..421f522 --- /dev/null +++ b/source/crc.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +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 _QUAKE_CRC_H +#define _QUAKE_CRC_H + +/* crc.h */ + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); +unsigned short CRC_Block (const byte *start, int count); //johnfitz -- texture crc + +#endif /* _QUAKE_CRC_H */ + diff --git a/source/cvar.c b/source/cvar.c new file mode 100644 index 0000000..aaa8089 --- /dev/null +++ b/source/cvar.c @@ -0,0 +1,652 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +static cvar_t *cvar_vars; +static char cvar_null_string[] = ""; + +//============================================================================== +// +// USER COMMANDS +// +//============================================================================== + +void Cvar_Reset (const char *name); //johnfitz + +/* +============ +Cvar_List_f -- johnfitz +============ +*/ +void Cvar_List_f (void) +{ + cvar_t *cvar; + const char *partial; + int len, count; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = Q_strlen(partial); + } + else + { + partial = NULL; + len = 0; + } + + count = 0; + for (cvar = cvar_vars ; cvar ; cvar = cvar->next) + { + if (partial && Q_strncmp(partial, cvar->name, len)) + { + continue; + } + Con_SafePrintf ("%s%s %s \"%s\"\n", + (cvar->flags & CVAR_ARCHIVE) ? "*" : " ", + (cvar->flags & CVAR_NOTIFY) ? "s" : " ", + cvar->name, + cvar->string); + count++; + } + + Con_SafePrintf ("%i cvars", count); + if (partial) + { + Con_SafePrintf (" beginning with \"%s\"", partial); + } + Con_SafePrintf ("\n"); +} + +/* +============ +Cvar_Inc_f -- johnfitz +============ +*/ +void Cvar_Inc_f (void) +{ + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("inc [amount] : increment cvar\n"); + break; + case 2: + Cvar_SetValue (Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) + 1); + break; + case 3: + Cvar_SetValue (Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) + Q_atof(Cmd_Argv(2))); + break; + } +} + +/* +============ +Cvar_Toggle_f -- johnfitz +============ +*/ +void Cvar_Toggle_f (void) +{ + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("toggle : toggle cvar\n"); + break; + case 2: + if (Cvar_VariableValue(Cmd_Argv(1))) + Cvar_Set (Cmd_Argv(1), "0"); + else + Cvar_Set (Cmd_Argv(1), "1"); + break; + } +} + +/* +============ +Cvar_Cycle_f -- johnfitz +============ +*/ +void Cvar_Cycle_f (void) +{ + int i; + + if (Cmd_Argc() < 3) + { + Con_Printf("cycle : cycle cvar through a list of values\n"); + return; + } + + //loop through the args until you find one that matches the current cvar value. + //yes, this will get stuck on a list that contains the same value twice. + //it's not worth dealing with, and i'm not even sure it can be dealt with. + for (i = 2; i < Cmd_Argc(); i++) + { + //zero is assumed to be a string, even though it could actually be zero. The worst case + //is that the first time you call this command, it won't match on zero when it should, but after that, + //it will be comparing strings that all had the same source (the user) so it will work. + if (Q_atof(Cmd_Argv(i)) == 0) + { + if (!strcmp(Cmd_Argv(i), Cvar_VariableString(Cmd_Argv(1)))) + break; + } + else + { + if (Q_atof(Cmd_Argv(i)) == Cvar_VariableValue(Cmd_Argv(1))) + break; + } + } + + if (i == Cmd_Argc()) + Cvar_Set (Cmd_Argv(1), Cmd_Argv(2)); // no match + else if (i + 1 == Cmd_Argc()) + Cvar_Set (Cmd_Argv(1), Cmd_Argv(2)); // matched last value in list + else + Cvar_Set (Cmd_Argv(1), Cmd_Argv(i+1)); // matched earlier in list +} + +/* +============ +Cvar_Reset_f -- johnfitz +============ +*/ +void Cvar_Reset_f (void) +{ + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf ("reset : reset cvar to default\n"); + break; + case 2: + Cvar_Reset (Cmd_Argv(1)); + break; + } +} + +/* +============ +Cvar_ResetAll_f -- johnfitz +============ +*/ +void Cvar_ResetAll_f (void) +{ + cvar_t *var; + + for (var = cvar_vars ; var ; var = var->next) + Cvar_Reset (var->name); +} + +/* +============ +Cvar_ResetCfg_f -- QuakeSpasm +============ +*/ +void Cvar_ResetCfg_f (void) +{ + cvar_t *var; + + for (var = cvar_vars ; var ; var = var->next) + if (var->flags & CVAR_ARCHIVE) Cvar_Reset (var->name); +} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============ +Cvar_Init -- johnfitz +============ +*/ + +void Cvar_Init (void) +{ + Cmd_AddCommand ("cvarlist", Cvar_List_f); + Cmd_AddCommand ("toggle", Cvar_Toggle_f); + Cmd_AddCommand ("cycle", Cvar_Cycle_f); + Cmd_AddCommand ("inc", Cvar_Inc_f); + Cmd_AddCommand ("reset", Cvar_Reset_f); + Cmd_AddCommand ("resetall", Cvar_ResetAll_f); + Cmd_AddCommand ("resetcfg", Cvar_ResetCfg_f); +} + +//============================================================================== +// +// CVAR FUNCTIONS +// +//============================================================================== + +/* +============ +Cvar_FindVar +============ +*/ +cvar_t *Cvar_FindVar (const char *var_name) +{ + cvar_t *var; + + for (var = cvar_vars ; var ; var = var->next) + { + if (!Q_strcmp(var_name, var->name)) + return var; + } + + return NULL; +} + +cvar_t *Cvar_FindVarAfter (const char *prev_name, unsigned int with_flags) +{ + cvar_t *var; + + if (*prev_name) + { + var = Cvar_FindVar (prev_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 & with_flags) || !with_flags) + break; + var = var->next; + } + return var; +} + +/* +============ +Cvar_LockVar +============ +*/ +void Cvar_LockVar (const char *var_name) +{ + cvar_t *var = Cvar_FindVar (var_name); + if (var) + var->flags |= CVAR_LOCKED; +} + +void Cvar_UnlockVar (const char *var_name) +{ + cvar_t *var = Cvar_FindVar (var_name); + if (var) + var->flags &= ~CVAR_LOCKED; +} + +void Cvar_UnlockAll (void) +{ + cvar_t *var; + + for (var = cvar_vars ; var ; var = var->next) + { + var->flags &= ~CVAR_LOCKED; + } +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return Q_atof (var->string); +} + + +/* +============ +Cvar_VariableString +============ +*/ +const char *Cvar_VariableString (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return cvar_null_string; + return var->string; +} + + +/* +============ +Cvar_CompleteVariable +============ +*/ +const char *Cvar_CompleteVariable (const char *partial) +{ + cvar_t *cvar; + int len; + + len = Q_strlen(partial); + if (!len) + return NULL; + +// check functions + for (cvar = cvar_vars ; cvar ; cvar = cvar->next) + { + if (!Q_strncmp(partial, cvar->name, len)) + return cvar->name; + } + + return NULL; +} + +/* +============ +Cvar_Reset -- johnfitz +============ +*/ +void Cvar_Reset (const char *name) +{ + cvar_t *var; + + var = Cvar_FindVar (name); + if (!var) + Con_Printf ("variable \"%s\" not found\n", name); + else + Cvar_SetQuick (var, var->default_string); +} + +void Cvar_SetQuick (cvar_t *var, const char *value) +{ + if (var->flags & (CVAR_ROM|CVAR_LOCKED)) + return; + if (!(var->flags & CVAR_REGISTERED)) + return; + + if (!var->string) + var->string = Z_Strdup (value); + else + { + int len; + + if (!strcmp(var->string, value)) + return; // no change + + var->flags |= CVAR_CHANGED; + len = Q_strlen (value); + if (len != Q_strlen(var->string)) + { + Z_Free ((void *)var->string); + var->string = (char *) Z_Malloc (len + 1); + } + memcpy ((char *)var->string, value, len + 1); + } + + var->value = Q_atof (var->string); + + //johnfitz -- save initial value for "reset" command + if (!var->default_string) + var->default_string = Z_Strdup (var->string); + //johnfitz -- during initialization, update default too + else if (!host_initialized) + { + // Sys_Printf("changing default of %s: %s -> %s\n", + // var->name, var->default_string, var->string); + Z_Free ((void *)var->default_string); + var->default_string = Z_Strdup (var->string); + } + //johnfitz + + if (var->callback) + var->callback (var); +} + +void Cvar_SetValueQuick (cvar_t *var, const float value) +{ + char val[32], *ptr = val; + + if (value == (float)((int)value)) + q_snprintf (val, sizeof(val), "%i", (int)value); + else + { + q_snprintf (val, sizeof(val), "%f", value); + // kill trailing zeroes + while (*ptr) + ptr++; + while (--ptr > val && *ptr == '0' && ptr[-1] != '.') + *ptr = '\0'; + } + + Cvar_SetQuick (var, val); +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set (const char *var_name, const char *value) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + { // there is an error in C code if this happens + Con_Printf ("Cvar_Set: variable %s not found\n", var_name); + return; + } + + Cvar_SetQuick (var, value); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue (const char *var_name, const float value) +{ + char val[32], *ptr = val; + + if (value == (float)((int)value)) + q_snprintf (val, sizeof(val), "%i", (int)value); + else + { + q_snprintf (val, sizeof(val), "%f", value); + // kill trailing zeroes + while (*ptr) + ptr++; + while (--ptr > val && *ptr == '0' && ptr[-1] != '.') + *ptr = '\0'; + } + + Cvar_Set (var_name, val); +} + +/* +============ +Cvar_SetROM +============ +*/ +void Cvar_SetROM (const char *var_name, const char *value) +{ + cvar_t *var = Cvar_FindVar (var_name); + if (var) + { + var->flags &= ~CVAR_ROM; + Cvar_SetQuick (var, value); + var->flags |= CVAR_ROM; + } +} + +/* +============ +Cvar_SetValueROM +============ +*/ +void Cvar_SetValueROM (const char *var_name, const float value) +{ + cvar_t *var = Cvar_FindVar (var_name); + if (var) + { + var->flags &= ~CVAR_ROM; + Cvar_SetValueQuick (var, value); + var->flags |= CVAR_ROM; + } +} + +/* +============ +Cvar_RegisterVariable + +Adds a freestanding variable to the variable list. +============ +*/ +void Cvar_RegisterVariable (cvar_t *variable) +{ + char value[512]; + qboolean set_rom; + cvar_t *cursor,*prev; //johnfitz -- sorted list insert + +// first check to see if it has already been defined + if (Cvar_FindVar (variable->name)) + { + Con_Printf ("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; + } + +// link the variable in + //johnfitz -- insert each entry in alphabetical order + if (cvar_vars == NULL || + strcmp(variable->name, cvar_vars->name) < 0) // insert at front + { + variable->next = cvar_vars; + cvar_vars = variable; + } + else //insert later + { + prev = cvar_vars; + cursor = cvar_vars->next; + while (cursor && (strcmp(variable->name, cursor->name) > 0)) + { + prev = cursor; + cursor = cursor->next; + } + variable->next = prev->next; + prev->next = variable; + } + //johnfitz + variable->flags |= CVAR_REGISTERED; + +// copy the value off, because future sets will Z_Free it + q_strlcpy (value, variable->string, sizeof(value)); + variable->string = NULL; + variable->default_string = NULL; + + if (!(variable->flags & CVAR_CALLBACK)) + variable->callback = NULL; + +// set it through the function to be consistent + set_rom = (variable->flags & CVAR_ROM); + variable->flags &= ~CVAR_ROM; + Cvar_SetQuick (variable, value); + if (set_rom) + variable->flags |= CVAR_ROM; +} + +/* +============ +Cvar_SetCallback + +Set a callback function to the var +============ +*/ +void Cvar_SetCallback (cvar_t *var, cvarcallback_t func) +{ + var->callback = func; + if (func) + var->flags |= CVAR_CALLBACK; + else var->flags &= ~CVAR_CALLBACK; +} + +/* +============ +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\"\n", v->name, v->string); + return true; + } + + Cvar_Set (v->name, Cmd_Argv(1)); + return true; +} + + +/* +============ +Cvar_WriteVariables + +Writes lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +void Cvar_WriteVariables (FILE *f) +{ + cvar_t *var; + + for (var = cvar_vars ; var ; var = var->next) + { + if (var->flags & CVAR_ARCHIVE) + fprintf (f, "%s \"%s\"\n", var->name, var->string); + } +} + diff --git a/source/cvar.h b/source/cvar.h new file mode 100644 index 0000000..495be2e --- /dev/null +++ b/source/cvar.h @@ -0,0 +1,143 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __CVAR_H__ +#define __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. + +*/ + +#define CVAR_NONE 0 +#define CVAR_ARCHIVE (1U << 0) // if set, causes it to be saved to config +#define CVAR_NOTIFY (1U << 1) // changes will be broadcasted to all players (q1) +#define CVAR_SERVERINFO (1U << 2) // added to serverinfo will be sent to clients (q1/net_dgrm.c and qwsv) +#define CVAR_USERINFO (1U << 3) // added to userinfo, will be sent to server (qwcl) +#define CVAR_CHANGED (1U << 4) +#define CVAR_ROM (1U << 6) +#define CVAR_LOCKED (1U << 8) // locked temporarily +#define CVAR_REGISTERED (1U << 10) // the var is added to the list of variables +#define CVAR_CALLBACK (1U << 16) // var has a callback + + +typedef void (*cvarcallback_t) (struct cvar_s *); + +typedef struct cvar_s +{ + const char *name; + const char *string; + unsigned int flags; + float value; + const char *default_string; //johnfitz -- remember defaults for reset function + cvarcallback_t callback; + struct cvar_s *next; +} cvar_t; + +void Cvar_RegisterVariable (cvar_t *variable); +// registers a cvar that already has the name, string, and optionally +// the archive elements set. + +void Cvar_SetCallback (cvar_t *var, cvarcallback_t func); +// set a callback function to the var + +void Cvar_Set (const char *var_name, const char *value); +// equivelant to " " typed at the console + +void Cvar_SetValue (const char *var_name, const float value); +// expands value to a string and calls Cvar_Set + +void Cvar_SetROM (const char *var_name, const char *value); +void Cvar_SetValueROM (const char *var_name, const float value); +// sets a CVAR_ROM variable from within the engine + +void Cvar_SetQuick (cvar_t *var, const char *value); +void Cvar_SetValueQuick (cvar_t *var, const float value); +// these two accept a cvar pointer instead of a var name, +// but are otherwise identical to the "non-Quick" versions. +// the cvar MUST be registered. + +float Cvar_VariableValue (const char *var_name); +// returns 0 if not defined or non numeric + +const char *Cvar_VariableString (const char *var_name); +// returns an empty string if not defined + +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_WriteVariables (FILE *f); +// Writes lines containing "set variable value" for all variables +// with the CVAR_ARCHIVE flag set + +cvar_t *Cvar_FindVar (const char *var_name); +cvar_t *Cvar_FindVarAfter (const char *prev_name, unsigned int with_flags); + +void Cvar_LockVar (const char *var_name); +void Cvar_UnlockVar (const char *var_name); +void Cvar_UnlockAll (void); + +void Cvar_Init (void); + +const char *Cvar_CompleteVariable (const char *partial); +// attempts to match a partial variable name for command line completion +// returns NULL if nothing fits + +#endif /* __CVAR_H__ */ + diff --git a/source/draw.h b/source/draw.h new file mode 100644 index 0000000..396f57e --- /dev/null +++ b/source/draw.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_DRAW_H +#define _QUAKE_DRAW_H + +// draw.h -- these are the only functions outside the refresh allowed +// to touch the vid buffer + +extern qpic_t *draw_disc; // also used on sbar + +void Draw_Init (void); +void Draw_Character (int x, int y, int num); +void Draw_DebugChar (char num); +void Draw_Pic (int x, int y, qpic_t *pic); +void Draw_TransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom); //johnfitz -- more parameters +void Draw_ConsoleBackground (void); //johnfitz -- removed parameter int lines +void Draw_TileClear (int x, int y, int w, int h); +void Draw_Fill (int x, int y, int w, int h, int c, float alpha); //johnfitz -- added alpha +void Draw_FadeScreen (void); +void Draw_String (int x, int y, const char *str); +qpic_t *Draw_PicFromWad (const char *name); +qpic_t *Draw_CachePic (const char *path); +void Draw_NewGame (void); + +void GL_SetCanvas (canvastype newcanvas); //johnfitz + +#endif /* _QUAKE_DRAW_H */ + diff --git a/source/filenames.h b/source/filenames.h new file mode 100644 index 0000000..9e4c78f --- /dev/null +++ b/source/filenames.h @@ -0,0 +1,193 @@ +/* Macros for taking apart, interpreting and processing file names. + * + * These are here because some non-Posix (a.k.a. DOSish) systems have + * drive letter brain-damage at the beginning of an absolute file name, + * use forward- and back-slash in path names interchangeably, and + * some of them have case-insensitive file names. + * + * This was based on filenames.h from BFD, the Binary File Descriptor + * library, Copyright (C) 2000-2016 Free Software Foundation, Inc., + * and changed by O. Sezer for our needs. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FILENAMES_H +#define FILENAMES_H + +#include + +/* ---------------------- Windows, DOS, OS2: ---------------------- */ +#if defined(__MSDOS__) || defined(__DOS__) || defined(__DJGPP__) || \ + defined(_MSDOS) || defined(__OS2__) || defined(__EMX__) || \ + defined(_WIN32) || defined(_Windows) || defined(__WINDOWS__) || \ + defined(__NT__) || defined(__CYGWIN__) + +#define HAVE_DOS_BASED_FILE_SYSTEM 1 +#define HAVE_CASE_INSENSITIVE_FILE_SYSTEM 1 + +#define HAS_DRIVE_SPEC(f) ((f)[0] && ((f)[1] == ':')) +#define STRIP_DRIVE_SPEC(f) ((f) + 2) +#define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') +/* both '/' and '\\' work as dir separator. djgpp likes changing + * '\\' into '/', so I define DIR_SEPARATOR_CHAR as '/' for djgpp, + * '\\' otherwise. */ +#ifdef __DJGPP__ +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#else +#define DIR_SEPARATOR_CHAR '\\' +#define DIR_SEPARATOR_STR "\\" +#endif +/* Note that IS_ABSOLUTE_PATH accepts d:foo as well, although it is + only semi-absolute. This is because the users of IS_ABSOLUTE_PATH + want to know whether to prepend the current working directory to + a file name, which should not be done with a name like d:foo. */ +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || HAS_DRIVE_SPEC((f))) + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strchr(_the_path, '/'); + char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strrchr(_the_path, '/'); + char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { +/* FIXME: What about C:FOO ? */ + const char *p1 = strchr(_the_path, '/'); + const char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { +/* FIXME: What about C:FOO ? */ + const char *p1 = strrchr(_the_path, '/'); + const char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strchr(_the_path, '/'); + char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strrchr(_the_path, '/'); + char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +#endif /* C++ */ + +/* ----------------- AmigaOS, MorphOS, AROS, etc: ----------------- */ +#elif defined(__MORPHOS__) || defined(__AROS__) || defined(AMIGAOS) || \ + defined(__amigaos__) || defined(__amigaos4__) ||defined(__amigados__) || \ + defined(AMIGA) || defined(_AMIGA) || defined(__AMIGA__) + +#define HAS_DRIVE_SPEC(f) (0) /* */ +#define STRIP_DRIVE_SPEC(f) (f) /* */ +#define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == ':') +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || (strchr((f), ':'))) +#define HAVE_CASE_INSENSITIVE_FILE_SYSTEM 1 + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { + char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { + char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { + const char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { + const char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { + char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { + char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +#endif /* C++ */ + +/* ---------------------- assumed UNIX-ish : ---------------------- */ +#else /* */ + +#define IS_DIR_SEPARATOR(c) ((c) == '/') +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0])) +#define HAS_DRIVE_SPEC(f) (0) +#define STRIP_DRIVE_SPEC(f) (f) + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { + return strrchr(_the_path, '/'); +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { + return strchr(_the_path, '/'); +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { + return strrchr(_the_path, '/'); +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { + return strrchr(_the_path, '/'); +} +#endif /* C++ */ + +#endif + +#endif /* FILENAMES_H */ diff --git a/source/gl_draw.c b/source/gl_draw.c new file mode 100644 index 0000000..0f67904 --- /dev/null +++ b/source/gl_draw.c @@ -0,0 +1,772 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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.c -- 2d drawing + +#include "quakedef.h" + +//extern unsigned char d_15to8table[65536]; //johnfitz -- never used + +cvar_t scr_conalpha = {"scr_conalpha", "0.5", CVAR_ARCHIVE}; //johnfitz + +qpic_t *draw_disc; +qpic_t *draw_backtile; + +gltexture_t *char_texture; //johnfitz +qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling +qpic_t *pic_nul; //johnfitz -- for missing gfx, don't crash + +//johnfitz -- new pics +byte pic_ovr_data[8][8] = +{ + {255,255,255,255,255,255,255,255}, + {255, 15, 15, 15, 15, 15, 15,255}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255,255, 2, 2, 2, 2, 2, 2}, +}; + +byte pic_ins_data[9][8] = +{ + { 15, 15,255,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + {255, 2, 2,255,255,255,255,255}, +}; + +byte pic_nul_data[8][8] = +{ + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, +}; + +byte pic_stipple_data[8][8] = +{ + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, +}; + +byte pic_crosshair_data[8][8] = +{ + {255,255,255,255,255,255,255,255}, + {255,255,255, 8, 9,255,255,255}, + {255,255,255, 6, 8, 2,255,255}, + {255, 6, 8, 8, 6, 8, 8,255}, + {255,255, 2, 8, 8, 2, 2, 2}, + {255,255,255, 7, 8, 2,255,255}, + {255,255,255,255, 2, 2,255,255}, + {255,255,255,255,255,255,255,255}, +}; +//johnfitz + +typedef struct +{ + gltexture_t *gltexture; + float sl, tl, sh, th; +} glpic_t; + +canvastype currentcanvas = CANVAS_NONE; //johnfitz -- for GL_SetCanvas + +//============================================================================== +// +// PIC CACHING +// +//============================================================================== + +typedef struct cachepic_s +{ + char name[MAX_QPATH]; + qpic_t pic; + byte padding[32]; // for appended glpic +} cachepic_t; + +#define MAX_CACHED_PICS 128 +cachepic_t menu_cachepics[MAX_CACHED_PICS]; +int menu_numcachepics; + +byte menuplyr_pixels[4096]; + +// scrap allocation +// Allocate all the little status bar obejcts into a single texture +// to crutch up stupid hardware / drivers + +#define MAX_SCRAPS 2 +#define BLOCK_WIDTH 256 +#define BLOCK_HEIGHT 256 + +int scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH]; +byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT]; //johnfitz -- removed *4 after BLOCK_HEIGHT +qboolean scrap_dirty; +gltexture_t *scrap_textures[MAX_SCRAPS]; //johnfitz + + +/* +================ +Scrap_AllocBlock + +returns an index into scrap_texnums[] and the position inside it +================ +*/ +int Scrap_AllocBlock (int w, int h, int *x, int *y) +{ + int i, j; + int best, best2; + int texnum; + + for (texnum=0 ; texnum= best) + break; + if (scrap_allocated[texnum][i+j] > best2) + best2 = scrap_allocated[texnum][i+j]; + } + if (j == w) + { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if (best + h > BLOCK_HEIGHT) + continue; + + for (i=0 ; iwidth < 64 && p->height < 64) + { + int x, y; + int i, j, k; + int texnum; + + texnum = Scrap_AllocBlock (p->width, p->height, &x, &y); + scrap_dirty = true; + k = 0; + for (i=0 ; iheight ; i++) + { + for (j=0 ; jwidth ; j++, k++) + scrap_texels[texnum][(y+i)*BLOCK_WIDTH + x + j] = p->data[k]; + } + gl.gltexture = scrap_textures[texnum]; //johnfitz -- changed to an array + //johnfitz -- no longer go from 0.01 to 0.99 + gl.sl = x/(float)BLOCK_WIDTH; + gl.sh = (x+p->width)/(float)BLOCK_WIDTH; + gl.tl = y/(float)BLOCK_WIDTH; + gl.th = (y+p->height)/(float)BLOCK_WIDTH; + } + else + { + char texturename[64]; //johnfitz + q_snprintf (texturename, sizeof(texturename), "%s:%s", WADFILENAME, name); //johnfitz + + offset = (src_offset_t)p - (src_offset_t)wad_base + sizeof(int)*2; //johnfitz + + gl.gltexture = TexMgr_LoadImage (NULL, texturename, p->width, p->height, SRC_INDEXED, p->data, WADFILENAME, + offset, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + gl.sl = 0; + gl.sh = (float)p->width/(float)TexMgr_PadConditional(p->width); //johnfitz + gl.tl = 0; + gl.th = (float)p->height/(float)TexMgr_PadConditional(p->height); //johnfitz + } + + memcpy (p->data, &gl, sizeof(glpic_t)); + + return p; +} + +/* +================ +Draw_CachePic +================ +*/ +qpic_t *Draw_CachePic (const char *path) +{ + cachepic_t *pic; + int i; + qpic_t *dat; + glpic_t gl; + + for (pic=menu_cachepics, i=0 ; iname)) + return &pic->pic; + } + if (menu_numcachepics == MAX_CACHED_PICS) + Sys_Error ("menu_numcachepics == MAX_CACHED_PICS"); + menu_numcachepics++; + strcpy (pic->name, path); + +// +// load the pic from disk +// + dat = (qpic_t *)COM_LoadTempFile (path, NULL); + if (!dat) + Sys_Error ("Draw_CachePic: failed to load %s", path); + SwapPic (dat); + + // HACK HACK HACK --- we need to keep the bytes for + // the translatable player picture just for the menu + // configuration dialog + if (!strcmp (path, "gfx/menuplyr.lmp")) + memcpy (menuplyr_pixels, dat->data, dat->width*dat->height); + + pic->pic.width = dat->width; + pic->pic.height = dat->height; + + gl.gltexture = TexMgr_LoadImage (NULL, path, dat->width, dat->height, SRC_INDEXED, dat->data, path, + sizeof(int)*2, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + gl.sl = 0; + gl.sh = (float)dat->width/(float)TexMgr_PadConditional(dat->width); //johnfitz + gl.tl = 0; + gl.th = (float)dat->height/(float)TexMgr_PadConditional(dat->height); //johnfitz + memcpy (pic->pic.data, &gl, sizeof(glpic_t)); + + return &pic->pic; +} + +/* +================ +Draw_MakePic -- johnfitz -- generate pics from internal data +================ +*/ +qpic_t *Draw_MakePic (const char *name, int width, int height, byte *data) +{ + int flags = TEXPREF_NEAREST | TEXPREF_ALPHA | TEXPREF_PERSIST | TEXPREF_NOPICMIP | TEXPREF_PAD; + qpic_t *pic; + glpic_t gl; + + pic = (qpic_t *) Hunk_Alloc (sizeof(qpic_t) - 4 + sizeof (glpic_t)); + pic->width = width; + pic->height = height; + + gl.gltexture = TexMgr_LoadImage (NULL, name, width, height, SRC_INDEXED, data, "", (src_offset_t)data, flags); + gl.sl = 0; + gl.sh = (float)width/(float)TexMgr_PadConditional(width); + gl.tl = 0; + gl.th = (float)height/(float)TexMgr_PadConditional(height); + memcpy (pic->data, &gl, sizeof(glpic_t)); + + return pic; +} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +=============== +Draw_LoadPics -- johnfitz +=============== +*/ +void Draw_LoadPics (void) +{ + byte *data; + src_offset_t offset; + + data = (byte *) W_GetLumpName ("conchars"); + if (!data) Sys_Error ("Draw_LoadPics: couldn't load conchars"); + offset = (src_offset_t)data - (src_offset_t)wad_base; + char_texture = TexMgr_LoadImage (NULL, WADFILENAME":conchars", 128, 128, SRC_INDEXED, data, + WADFILENAME, offset, TEXPREF_ALPHA | TEXPREF_NEAREST | TEXPREF_NOPICMIP | TEXPREF_CONCHARS); + + draw_disc = Draw_PicFromWad ("disc"); + draw_backtile = Draw_PicFromWad ("backtile"); +} + +/* +=============== +Draw_NewGame -- johnfitz +=============== +*/ +void Draw_NewGame (void) +{ + cachepic_t *pic; + int i; + + // empty scrap and reallocate gltextures + memset(scrap_allocated, 0, sizeof(scrap_allocated)); + memset(scrap_texels, 255, sizeof(scrap_texels)); + + Scrap_Upload (); //creates 2 empty gltextures + + // reload wad pics + W_LoadWadFile (); //johnfitz -- filename is now hard-coded for honesty + Draw_LoadPics (); + SCR_LoadPics (); + Sbar_LoadPics (); + + // empty lmp cache + for (pic = menu_cachepics, i = 0; i < menu_numcachepics; pic++, i++) + pic->name[0] = 0; + menu_numcachepics = 0; +} + +/* +=============== +Draw_Init -- johnfitz -- rewritten +=============== +*/ +void Draw_Init (void) +{ + Cvar_RegisterVariable (&scr_conalpha); + + // clear scrap and allocate gltextures + memset(scrap_allocated, 0, sizeof(scrap_allocated)); + memset(scrap_texels, 255, sizeof(scrap_texels)); + + Scrap_Upload (); //creates 2 empty textures + + // create internal pics + pic_ins = Draw_MakePic ("ins", 8, 9, &pic_ins_data[0][0]); + pic_ovr = Draw_MakePic ("ovr", 8, 8, &pic_ovr_data[0][0]); + pic_nul = Draw_MakePic ("nul", 8, 8, &pic_nul_data[0][0]); + + // load game pics + Draw_LoadPics (); +} + +//============================================================================== +// +// 2D DRAWING +// +//============================================================================== + +/* +================ +Draw_CharacterQuad -- johnfitz -- seperate function to spit out verts +================ +*/ +void Draw_CharacterQuad (int x, int y, char num) +{ + int row, col; + float frow, fcol, size; + + row = num>>4; + col = num&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + glTexCoord2f (fcol, frow); + glVertex2f (x, y); + glTexCoord2f (fcol + size, frow); + glVertex2f (x+8, y); + glTexCoord2f (fcol + size, frow + size); + glVertex2f (x+8, y+8); + glTexCoord2f (fcol, frow + size); + glVertex2f (x, y+8); +} + +/* +================ +Draw_Character -- johnfitz -- modified to call Draw_CharacterQuad +================ +*/ +void Draw_Character (int x, int y, int num) +{ + if (y <= -8) + return; // totally off screen + + num &= 255; + + if (num == 32) + return; //don't waste verts on spaces + + GL_Bind (char_texture); + glBegin (GL_QUADS); + + Draw_CharacterQuad (x, y, (char) num); + + glEnd (); +} + +/* +================ +Draw_String -- johnfitz -- modified to call Draw_CharacterQuad +================ +*/ +void Draw_String (int x, int y, const char *str) +{ + if (y <= -8) + return; // totally off screen + + GL_Bind (char_texture); + glBegin (GL_QUADS); + + while (*str) + { + if (*str != 32) //don't waste verts on spaces + Draw_CharacterQuad (x, y, *str); + str++; + x += 8; + } + + glEnd (); +} + +/* +============= +Draw_Pic -- johnfitz -- modified +============= +*/ +void Draw_Pic (int x, int y, qpic_t *pic) +{ + glpic_t *gl; + + if (scrap_dirty) + Scrap_Upload (); + gl = (glpic_t *)pic->data; + GL_Bind (gl->gltexture); + glBegin (GL_QUADS); + glTexCoord2f (gl->sl, gl->tl); + glVertex2f (x, y); + glTexCoord2f (gl->sh, gl->tl); + glVertex2f (x+pic->width, y); + glTexCoord2f (gl->sh, gl->th); + glVertex2f (x+pic->width, y+pic->height); + glTexCoord2f (gl->sl, gl->th); + glVertex2f (x, y+pic->height); + glEnd (); +} + +/* +============= +Draw_TransPicTranslate -- johnfitz -- rewritten to use texmgr to do translation + +Only used for the player color selection menu +============= +*/ +void Draw_TransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom) +{ + static int oldtop = -2; + static int oldbottom = -2; + + if (top != oldtop || bottom != oldbottom) + { + glpic_t *p = (glpic_t *)pic->data; + gltexture_t *glt = p->gltexture; + oldtop = top; + oldbottom = bottom; + TexMgr_ReloadImage (glt, top, bottom); + } + Draw_Pic (x, y, pic); +} + +/* +================ +Draw_ConsoleBackground -- johnfitz -- rewritten +================ +*/ +void Draw_ConsoleBackground (void) +{ + qpic_t *pic; + float alpha; + + pic = Draw_CachePic ("gfx/conback.lmp"); + pic->width = vid.conwidth; + pic->height = vid.conheight; + + alpha = (con_forcedup) ? 1.0 : scr_conalpha.value; + + GL_SetCanvas (CANVAS_CONSOLE); //in case this is called from weird places + + if (alpha > 0.0) + { + if (alpha < 1.0) + { + glEnable (GL_BLEND); + glColor4f (1,1,1,alpha); + glDisable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } + + Draw_Pic (0, 0, pic); + + if (alpha < 1.0) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glEnable (GL_ALPHA_TEST); + glDisable (GL_BLEND); + glColor4f (1,1,1,1); + } + } +} + + +/* +============= +Draw_TileClear + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +void Draw_TileClear (int x, int y, int w, int h) +{ + glpic_t *gl; + + gl = (glpic_t *)draw_backtile->data; + + glColor3f (1,1,1); + GL_Bind (gl->gltexture); + glBegin (GL_QUADS); + glTexCoord2f (x/64.0, y/64.0); + glVertex2f (x, y); + glTexCoord2f ( (x+w)/64.0, y/64.0); + glVertex2f (x+w, y); + glTexCoord2f ( (x+w)/64.0, (y+h)/64.0); + glVertex2f (x+w, y+h); + glTexCoord2f ( x/64.0, (y+h)/64.0 ); + glVertex2f (x, y+h); + glEnd (); +} + +/* +============= +Draw_Fill + +Fills a box of pixels with a single color +============= +*/ +void Draw_Fill (int x, int y, int w, int h, int c, float alpha) //johnfitz -- added alpha +{ + byte *pal = (byte *)d_8to24table; //johnfitz -- use d_8to24table instead of host_basepal + + glDisable (GL_TEXTURE_2D); + glEnable (GL_BLEND); //johnfitz -- for alpha + glDisable (GL_ALPHA_TEST); //johnfitz -- for alpha + glColor4f (pal[c*4]/255.0, pal[c*4+1]/255.0, pal[c*4+2]/255.0, alpha); //johnfitz -- added alpha + + glBegin (GL_QUADS); + glVertex2f (x,y); + glVertex2f (x+w, y); + glVertex2f (x+w, y+h); + glVertex2f (x, y+h); + glEnd (); + + glColor3f (1,1,1); + glDisable (GL_BLEND); //johnfitz -- for alpha + glEnable (GL_ALPHA_TEST); //johnfitz -- for alpha + glEnable (GL_TEXTURE_2D); +} + +/* +================ +Draw_FadeScreen -- johnfitz -- revised +================ +*/ +void Draw_FadeScreen (void) +{ + GL_SetCanvas (CANVAS_DEFAULT); + + glEnable (GL_BLEND); + glDisable (GL_ALPHA_TEST); + glDisable (GL_TEXTURE_2D); + glColor4f (0, 0, 0, 0.5); + glBegin (GL_QUADS); + + glVertex2f (0,0); + glVertex2f (glwidth, 0); + glVertex2f (glwidth, glheight); + glVertex2f (0, glheight); + + glEnd (); + glColor4f (1,1,1,1); + glEnable (GL_TEXTURE_2D); + glEnable (GL_ALPHA_TEST); + glDisable (GL_BLEND); + + Sbar_Changed(); +} + +/* +================ +GL_SetCanvas -- johnfitz -- support various canvas types +================ +*/ +void GL_SetCanvas (canvastype newcanvas) +{ + extern vrect_t scr_vrect; + float s; + int lines; + + if (newcanvas == currentcanvas) + return; + + currentcanvas = newcanvas; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + + switch(newcanvas) + { + case CANVAS_DEFAULT: + glOrtho (0, glwidth, glheight, 0, -99999, 99999); + glViewport (glx, gly, glwidth, glheight); + break; + case CANVAS_CONSOLE: + lines = vid.conheight - (scr_con_current * vid.conheight / glheight); + glOrtho (0, vid.conwidth, vid.conheight + lines, lines, -99999, 99999); + glViewport (glx, gly, glwidth, glheight); + break; + case CANVAS_MENU: + s = q_min((float)glwidth / 320.0, (float)glheight / 200.0); + s = CLAMP (1.0, scr_menuscale.value, s); + // ericw -- doubled width to 640 to accommodate long keybindings + glOrtho (0, 640, 200, 0, -99999, 99999); + glViewport (glx + (glwidth - 320*s) / 2, gly + (glheight - 200*s) / 2, 640*s, 200*s); + break; + case CANVAS_SBAR: + s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + if (cl.gametype == GAME_DEATHMATCH) + { + glOrtho (0, glwidth / s, 48, 0, -99999, 99999); + glViewport (glx, gly, glwidth, 48*s); + } + else + { + glOrtho (0, 320, 48, 0, -99999, 99999); + glViewport (glx + (glwidth - 320*s) / 2, gly, 320*s, 48*s); + } + break; + case CANVAS_WARPIMAGE: + glOrtho (0, 128, 0, 128, -99999, 99999); + glViewport (glx, gly+glheight-gl_warpimagesize, gl_warpimagesize, gl_warpimagesize); + break; + case CANVAS_CROSSHAIR: //0,0 is center of viewport + s = CLAMP (1.0, scr_crosshairscale.value, 10.0); + glOrtho (scr_vrect.width/-2/s, scr_vrect.width/2/s, scr_vrect.height/2/s, scr_vrect.height/-2/s, -99999, 99999); + glViewport (scr_vrect.x, glheight - scr_vrect.y - scr_vrect.height, scr_vrect.width & ~1, scr_vrect.height & ~1); + break; + case CANVAS_BOTTOMLEFT: //used by devstats + s = (float)glwidth/vid.conwidth; //use console scale + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx, gly, 320*s, 200*s); + break; + case CANVAS_BOTTOMRIGHT: //used by fps/clock + s = (float)glwidth/vid.conwidth; //use console scale + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx+glwidth-320*s, gly, 320*s, 200*s); + break; + case CANVAS_TOPRIGHT: //used by disc + s = 1; + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx+glwidth-320*s, gly+glheight-200*s, 320*s, 200*s); + break; + default: + Sys_Error ("GL_SetCanvas: bad canvas type"); + } + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity (); +} + +/* +================ +GL_Set2D -- johnfitz -- rewritten +================ +*/ +void GL_Set2D (void) +{ + currentcanvas = CANVAS_INVALID; + GL_SetCanvas (CANVAS_DEFAULT); + + glDisable (GL_DEPTH_TEST); + glDisable (GL_CULL_FACE); + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + glColor4f (1,1,1,1); +} diff --git a/source/gl_fog.c b/source/gl_fog.c new file mode 100644 index 0000000..e0e3996 --- /dev/null +++ b/source/gl_fog.c @@ -0,0 +1,410 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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. +*/ +//gl_fog.c -- global and volumetric fog + +#include "quakedef.h" + +//============================================================================== +// +// GLOBAL FOG +// +//============================================================================== + +#define DEFAULT_DENSITY 0.0 +#define DEFAULT_GRAY 0.3 + +float fog_density; +float fog_red; +float fog_green; +float fog_blue; + +float old_density; +float old_red; +float old_green; +float old_blue; + +float fade_time; //duration of fade +float fade_done; //time when fade will be done + +/* +============= +Fog_Update + +update internal variables +============= +*/ +void Fog_Update (float density, float red, float green, float blue, float time) +{ + //save previous settings for fade + if (time > 0) + { + //check for a fade in progress + if (fade_done > cl.time) + { + float f; + + f = (fade_done - cl.time) / fade_time; + old_density = f * old_density + (1.0 - f) * fog_density; + old_red = f * old_red + (1.0 - f) * fog_red; + old_green = f * old_green + (1.0 - f) * fog_green; + old_blue = f * old_blue + (1.0 - f) * fog_blue; + } + else + { + old_density = fog_density; + old_red = fog_red; + old_green = fog_green; + old_blue = fog_blue; + } + } + + fog_density = density; + fog_red = red; + fog_green = green; + fog_blue = blue; + fade_time = time; + fade_done = cl.time + time; +} + +/* +============= +Fog_ParseServerMessage + +handle an SVC_FOG message from server +============= +*/ +void Fog_ParseServerMessage (void) +{ + float density, red, green, blue, time; + + density = MSG_ReadByte() / 255.0; + red = MSG_ReadByte() / 255.0; + green = MSG_ReadByte() / 255.0; + blue = MSG_ReadByte() / 255.0; + time = q_max(0.0, MSG_ReadShort() / 100.0); + + Fog_Update (density, red, green, blue, time); +} + +/* +============= +Fog_FogCommand_f + +handle the 'fog' console command +============= +*/ +void Fog_FogCommand_f (void) +{ + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("usage:\n"); + Con_Printf(" fog \n"); + Con_Printf(" fog \n"); + Con_Printf(" fog \n"); + Con_Printf("current values:\n"); + Con_Printf(" \"density\" is \"%f\"\n", fog_density); + Con_Printf(" \"red\" is \"%f\"\n", fog_red); + Con_Printf(" \"green\" is \"%f\"\n", fog_green); + Con_Printf(" \"blue\" is \"%f\"\n", fog_blue); + break; + case 2: + Fog_Update(q_max(0.0, atof(Cmd_Argv(1))), + fog_red, + fog_green, + fog_blue, + 0.0); + break; + case 3: //TEST + Fog_Update(q_max(0.0, atof(Cmd_Argv(1))), + fog_red, + fog_green, + fog_blue, + atof(Cmd_Argv(2))); + break; + case 4: + Fog_Update(fog_density, + CLAMP(0.0, atof(Cmd_Argv(1)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(2)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(3)), 1.0), + 0.0); + break; + case 5: + Fog_Update(q_max(0.0, atof(Cmd_Argv(1))), + CLAMP(0.0, atof(Cmd_Argv(2)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(3)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(4)), 1.0), + 0.0); + break; + case 6: //TEST + Fog_Update(q_max(0.0, atof(Cmd_Argv(1))), + CLAMP(0.0, atof(Cmd_Argv(2)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(3)), 1.0), + CLAMP(0.0, atof(Cmd_Argv(4)), 1.0), + atof(Cmd_Argv(5))); + break; + } +} + +/* +============= +Fog_ParseWorldspawn + +called at map load +============= +*/ +void Fog_ParseWorldspawn (void) +{ + char key[128], value[4096]; + const char *data; + + //initially no fog + fog_density = DEFAULT_DENSITY; + fog_red = DEFAULT_GRAY; + fog_green = DEFAULT_GRAY; + fog_blue = DEFAULT_GRAY; + + old_density = DEFAULT_DENSITY; + old_red = DEFAULT_GRAY; + old_green = DEFAULT_GRAY; + old_blue = DEFAULT_GRAY; + + fade_time = 0.0; + fade_done = 0.0; + + data = COM_Parse(cl.worldmodel->entities); + if (!data) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strcpy(key, com_token + 1); + else + strcpy(key, com_token); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_Parse(data); + if (!data) + return; // error + strcpy(value, com_token); + + if (!strcmp("fog", key)) + { + sscanf(value, "%f %f %f %f", &fog_density, &fog_red, &fog_green, &fog_blue); + } + } +} + +/* +============= +Fog_GetColor + +calculates fog color for this frame, taking into account fade times +============= +*/ +float *Fog_GetColor (void) +{ + static float c[4]; + float f; + int i; + + if (fade_done > cl.time) + { + f = (fade_done - cl.time) / fade_time; + c[0] = f * old_red + (1.0 - f) * fog_red; + c[1] = f * old_green + (1.0 - f) * fog_green; + c[2] = f * old_blue + (1.0 - f) * fog_blue; + c[3] = 1.0; + } + else + { + c[0] = fog_red; + c[1] = fog_green; + c[2] = fog_blue; + c[3] = 1.0; + } + + //find closest 24-bit RGB value, so solid-colored sky can match the fog perfectly + for (i=0;i<3;i++) + c[i] = (float)(Q_rint(c[i] * 255)) / 255.0f; + + return c; +} + +/* +============= +Fog_GetDensity + +returns current density of fog +============= +*/ +float Fog_GetDensity (void) +{ + float f; + + if (fade_done > cl.time) + { + f = (fade_done - cl.time) / fade_time; + return f * old_density + (1.0 - f) * fog_density; + } + else + return fog_density; +} + +/* +============= +Fog_SetupFrame + +called at the beginning of each frame +============= +*/ +void Fog_SetupFrame (void) +{ + glFogfv(GL_FOG_COLOR, Fog_GetColor()); + glFogf(GL_FOG_DENSITY, Fog_GetDensity() / 64.0); +} + +/* +============= +Fog_EnableGFog + +called before drawing stuff that should be fogged +============= +*/ +void Fog_EnableGFog (void) +{ + if (Fog_GetDensity() > 0) + glEnable(GL_FOG); +} + +/* +============= +Fog_DisableGFog + +called after drawing stuff that should be fogged +============= +*/ +void Fog_DisableGFog (void) +{ + if (Fog_GetDensity() > 0) + glDisable(GL_FOG); +} + +/* +============= +Fog_StartAdditive + +called before drawing stuff that is additive blended -- sets fog color to black +============= +*/ +void Fog_StartAdditive (void) +{ + vec3_t color = {0,0,0}; + + if (Fog_GetDensity() > 0) + glFogfv(GL_FOG_COLOR, color); +} + +/* +============= +Fog_StopAdditive + +called after drawing stuff that is additive blended -- restores fog color +============= +*/ +void Fog_StopAdditive (void) +{ + if (Fog_GetDensity() > 0) + glFogfv(GL_FOG_COLOR, Fog_GetColor()); +} + +//============================================================================== +// +// VOLUMETRIC FOG +// +//============================================================================== + +cvar_t r_vfog = {"r_vfog", "1", CVAR_NONE}; + +void Fog_DrawVFog (void){} +void Fog_MarkModels (void){} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============= +Fog_NewMap + +called whenever a map is loaded +============= +*/ +void Fog_NewMap (void) +{ + Fog_ParseWorldspawn (); //for global fog + Fog_MarkModels (); //for volumetric fog +} + +/* +============= +Fog_Init + +called when quake initializes +============= +*/ +void Fog_Init (void) +{ + Cmd_AddCommand ("fog",Fog_FogCommand_f); + + //Cvar_RegisterVariable (&r_vfog); + + //set up global fog + fog_density = DEFAULT_DENSITY; + fog_red = DEFAULT_GRAY; + fog_green = DEFAULT_GRAY; + fog_blue = DEFAULT_GRAY; + + Fog_SetupState (); +} + +/* +============= +Fog_SetupState + +ericw -- moved from Fog_Init, state that needs to be setup when a new context is created +============= +*/ +void Fog_SetupState (void) +{ + glFogi(GL_FOG_MODE, GL_EXP2); +} diff --git a/source/gl_mesh.c b/source/gl_mesh.c new file mode 100644 index 0000000..c362fce --- /dev/null +++ b/source/gl_mesh.c @@ -0,0 +1,614 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +// gl_mesh.c: triangle model functions + +#include "quakedef.h" + + +/* +================================================================= + +ALIAS MODEL DISPLAY LIST GENERATION + +================================================================= +*/ + +qmodel_t *aliasmodel; +aliashdr_t *paliashdr; + +int used[8192]; // qboolean + +// the command list holds counts and s/t values that are valid for +// every frame +int commands[8192]; +int numcommands; + +// all frames will have their vertexes rearranged and expanded +// so they are in the order expected by the command list +int vertexorder[8192]; +int numorder; + +int allverts, alltris; + +int stripverts[128]; +int striptris[128]; +int stripcount; + +/* +================ +StripLength +================ +*/ +int StripLength (int starttri, int startv) +{ + int m1, m2; + int j; + mtriangle_t *last, *check; + int k; + + used[starttri] = 2; + + last = &triangles[starttri]; + + stripverts[0] = last->vertindex[(startv)%3]; + stripverts[1] = last->vertindex[(startv+1)%3]; + stripverts[2] = last->vertindex[(startv+2)%3]; + + striptris[0] = starttri; + stripcount = 1; + + m1 = last->vertindex[(startv+2)%3]; + m2 = last->vertindex[(startv+1)%3]; + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) + { + if (check->facesfront != last->facesfront) + continue; + for (k=0 ; k<3 ; k++) + { + if (check->vertindex[k] != m1) + continue; + if (check->vertindex[ (k+1)%3 ] != m2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + if (stripcount & 1) + m2 = check->vertindex[ (k+2)%3 ]; + else + m1 = check->vertindex[ (k+2)%3 ]; + + stripverts[stripcount+2] = check->vertindex[ (k+2)%3 ]; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + +/* +=========== +FanLength +=========== +*/ +int FanLength (int starttri, int startv) +{ + int m1, m2; + int j; + mtriangle_t *last, *check; + int k; + + used[starttri] = 2; + + last = &triangles[starttri]; + + stripverts[0] = last->vertindex[(startv)%3]; + stripverts[1] = last->vertindex[(startv+1)%3]; + stripverts[2] = last->vertindex[(startv+2)%3]; + + striptris[0] = starttri; + stripcount = 1; + + m1 = last->vertindex[(startv+0)%3]; + m2 = last->vertindex[(startv+2)%3]; + + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) + { + if (check->facesfront != last->facesfront) + continue; + for (k=0 ; k<3 ; k++) + { + if (check->vertindex[k] != m1) + continue; + if (check->vertindex[ (k+1)%3 ] != m2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + m2 = check->vertindex[ (k+2)%3 ]; + + stripverts[stripcount+2] = m2; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + + +/* +================ +BuildTris + +Generate a list of trifans or strips +for the model, which holds for all frames +================ +*/ +void BuildTris (void) +{ + int i, j, k; + int startv; + float s, t; + int len, bestlen, besttype; + int bestverts[1024]; + int besttris[1024]; + int type; + + // + // build tristrips + // + numorder = 0; + numcommands = 0; + memset (used, 0, sizeof(used)); + for (i = 0; i < pheader->numtris; i++) + { + // pick an unused triangle and start the trifan + if (used[i]) + continue; + + bestlen = 0; + besttype = 0; + for (type = 0 ; type < 2 ; type++) +// type = 1; + { + for (startv = 0; startv < 3; startv++) + { + if (type == 1) + len = StripLength (i, startv); + else + len = FanLength (i, startv); + if (len > bestlen) + { + besttype = type; + bestlen = len; + for (j = 0; j < bestlen+2; j++) + bestverts[j] = stripverts[j]; + for (j = 0; j < bestlen; j++) + besttris[j] = striptris[j]; + } + } + } + + // mark the tris on the best strip as used + for (j = 0; j < bestlen; j++) + used[besttris[j]] = 1; + + if (besttype == 1) + commands[numcommands++] = (bestlen+2); + else + commands[numcommands++] = -(bestlen+2); + + for (j = 0; j < bestlen+2; j++) + { + int tmp; + + // emit a vertex into the reorder buffer + k = bestverts[j]; + vertexorder[numorder++] = k; + + // emit s/t coords into the commands stream + s = stverts[k].s; + t = stverts[k].t; + if (!triangles[besttris[0]].facesfront && stverts[k].onseam) + s += pheader->skinwidth / 2; // on back side + s = (s + 0.5) / pheader->skinwidth; + t = (t + 0.5) / pheader->skinheight; + + // *(float *)&commands[numcommands++] = s; + // *(float *)&commands[numcommands++] = t; + // NOTE: 4 == sizeof(int) + // == sizeof(float) + memcpy (&tmp, &s, 4); + commands[numcommands++] = tmp; + memcpy (&tmp, &t, 4); + commands[numcommands++] = tmp; + } + } + + commands[numcommands++] = 0; // end of list marker + + Con_DPrintf2 ("%3i tri %3i vert %3i cmd\n", pheader->numtris, numorder, numcommands); + + allverts += numorder; + alltris += pheader->numtris; +} + +static void GL_MakeAliasModelDisplayLists_VBO (void); +static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr); + +/* +================ +GL_MakeAliasModelDisplayLists +================ +*/ +void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr) +{ + int i, j; + int *cmds; + trivertx_t *verts; + float hscale, vscale; //johnfitz -- padded skins + int count; //johnfitz -- precompute texcoords for padded skins + int *loadcmds; //johnfitz + + //johnfitz -- padded skins + hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); + vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); + //johnfitz + + aliasmodel = m; + paliashdr = hdr; // (aliashdr_t *)Mod_Extradata (m); + +//johnfitz -- generate meshes + Con_DPrintf2 ("meshing %s...\n",m->name); + BuildTris (); + + // save the data out + + paliashdr->poseverts = numorder; + + cmds = (int *) Hunk_Alloc (numcommands * 4); + paliashdr->commands = (byte *)cmds - (byte *)paliashdr; + + //johnfitz -- precompute texcoords for padded skins + loadcmds = commands; + while(1) + { + *cmds++ = count = *loadcmds++; + + if (!count) + break; + + if (count < 0) + count = -count; + + do + { + *(float *)cmds++ = hscale * (*(float *)loadcmds++); + *(float *)cmds++ = vscale * (*(float *)loadcmds++); + } while (--count); + } + //johnfitz + + verts = (trivertx_t *) Hunk_Alloc (paliashdr->numposes * paliashdr->poseverts * sizeof(trivertx_t)); + paliashdr->posedata = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumposes * paliashdr->numverts * sizeof(trivertx_t)); + paliashdr->vertexes = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumverts ; j++) + verts[i*paliashdr->numverts + j] = poseverts[i][j]; + + // there can never be more than this number of verts and we just put them all on the hunk + maxverts_vbo = pheader->numtris * 3; + desc = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * maxverts_vbo); + + // there will always be this number of indexes + indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * maxverts_vbo); + + pheader->indexes = (intptr_t) indexes - (intptr_t) pheader; + pheader->meshdesc = (intptr_t) desc - (intptr_t) pheader; + pheader->numindexes = 0; + pheader->numverts_vbo = 0; + + for (i = 0; i < pheader->numtris; i++) + { + for (j = 0; j < 3; j++) + { + int v; + + // index into hdr->vertexes + unsigned short vertindex = triangles[i].vertindex[j]; + + // basic s/t coords + int s = stverts[vertindex].s; + int t = stverts[vertindex].t; + + // check for back side and adjust texcoord s + if (!triangles[i].facesfront && stverts[vertindex].onseam) s += pheader->skinwidth / 2; + + // see does this vert already exist + for (v = 0; v < pheader->numverts_vbo; v++) + { + // it could use the same xyz but have different s and t + if (desc[v].vertindex == vertindex && (int) desc[v].st[0] == s && (int) desc[v].st[1] == t) + { + // exists; emit an index for it + indexes[pheader->numindexes++] = v; + + // no need to check any more + break; + } + } + + if (v == pheader->numverts_vbo) + { + // doesn't exist; emit a new vert and index + indexes[pheader->numindexes++] = pheader->numverts_vbo; + + desc[pheader->numverts_vbo].vertindex = vertindex; + desc[pheader->numverts_vbo].st[0] = s; + desc[pheader->numverts_vbo++].st[1] = t; + } + } + } + + // upload immediately + GLMesh_LoadVertexBuffer (aliasmodel, pheader); +} + +#define NUMVERTEXNORMALS 162 +extern float r_avertexnormals[NUMVERTEXNORMALS][3]; + +/* +================ +GLMesh_LoadVertexBuffer + +Upload the given alias model's mesh to a VBO + +Original code by MH from RMQEngine +================ +*/ +static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr) +{ + int totalvbosize = 0; + const aliasmesh_t *desc; + const short *indexes; + const trivertx_t *trivertexes; + byte *vbodata; + int f; + + if (!gl_glsl_alias_able) + return; + +// count the sizes we need + + // ericw -- RMQEngine stored these vbo*ofs values in aliashdr_t, but we must not + // mutate Mod_Extradata since it might be reloaded from disk, so I moved them to qmodel_t + // (test case: roman1.bsp from arwop, 64mb heap) + m->vboindexofs = 0; + + m->vboxyzofs = 0; + totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + + m->vbostofs = totalvbosize; + totalvbosize += (hdr->numverts_vbo * sizeof (meshst_t)); + + if (!hdr->numindexes) return; + if (!totalvbosize) return; + +// grab the pointers to data in the extradata + + desc = (aliasmesh_t *) ((byte *) hdr + hdr->meshdesc); + indexes = (short *) ((byte *) hdr + hdr->indexes); + trivertexes = (trivertx_t *) ((byte *)hdr + hdr->vertexes); + +// upload indices buffer + + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + GL_GenBuffersFunc (1, &m->meshindexesvbo); + GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, m->meshindexesvbo); + GL_BufferDataFunc (GL_ELEMENT_ARRAY_BUFFER, hdr->numindexes * sizeof (unsigned short), indexes, GL_STATIC_DRAW); + +// create the vertex buffer (empty) + + vbodata = (byte *) malloc(totalvbosize); + memset(vbodata, 0, totalvbosize); + +// fill in the vertices at the start of the buffer + for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + { + int v; + meshxyz_t *xyz = (meshxyz_t *) (vbodata + (f * hdr->numverts_vbo * sizeof (meshxyz_t))); + const trivertx_t *tv = trivertexes + (hdr->numverts * f); + + for (v = 0; v < hdr->numverts_vbo; v++) + { + trivertx_t trivert = tv[desc[v].vertindex]; + + xyz[v].xyz[0] = trivert.v[0]; + xyz[v].xyz[1] = trivert.v[1]; + xyz[v].xyz[2] = trivert.v[2]; + xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression + + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + xyz[v].normal[0] = 127 * r_avertexnormals[trivert.lightnormalindex][0]; + xyz[v].normal[1] = 127 * r_avertexnormals[trivert.lightnormalindex][1]; + xyz[v].normal[2] = 127 * r_avertexnormals[trivert.lightnormalindex][2]; + xyz[v].normal[3] = 0; // unused; for 4-byte alignment + } + } + +// fill in the ST coords at the end of the buffer + { + meshst_t *st; + float hscale, vscale; + + //johnfitz -- padded skins + hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); + vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); + //johnfitz + + st = (meshst_t *) (vbodata + m->vbostofs); + for (f = 0; f < hdr->numverts_vbo; f++) + { + st[f].st[0] = hscale * ((float) desc[f].st[0] + 0.5f) / (float) hdr->skinwidth; + st[f].st[1] = vscale * ((float) desc[f].st[1] + 0.5f) / (float) hdr->skinheight; + } + } + +// upload vertexes buffer + GL_DeleteBuffersFunc (1, &m->meshvbo); + GL_GenBuffersFunc (1, &m->meshvbo); + GL_BindBufferFunc (GL_ARRAY_BUFFER, m->meshvbo); + GL_BufferDataFunc (GL_ARRAY_BUFFER, totalvbosize, vbodata, GL_STATIC_DRAW); + + free (vbodata); + +// invalidate the cached bindings + GL_ClearBufferBindings (); +} + +/* +================ +GLMesh_LoadVertexBuffers + +Loop over all precached alias models, and upload each one to a VBO. +================ +*/ +void GLMesh_LoadVertexBuffers (void) +{ + int j; + qmodel_t *m; + const aliashdr_t *hdr; + + if (!gl_glsl_alias_able) + return; + + for (j = 1; j < MAX_MODELS; j++) + { + if (!(m = cl.model_precache[j])) break; + if (m->type != mod_alias) continue; + + hdr = (const aliashdr_t *) Mod_Extradata (m); + + GLMesh_LoadVertexBuffer (m, hdr); + } +} + +/* +================ +GLMesh_DeleteVertexBuffers + +Delete VBOs for all loaded alias models +================ +*/ +void GLMesh_DeleteVertexBuffers (void) +{ + int j; + qmodel_t *m; + + if (!gl_glsl_alias_able) + return; + + for (j = 1; j < MAX_MODELS; j++) + { + if (!(m = cl.model_precache[j])) break; + if (m->type != mod_alias) continue; + + GL_DeleteBuffersFunc (1, &m->meshvbo); + m->meshvbo = 0; + + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + m->meshindexesvbo = 0; + } + + GL_ClearBufferBindings (); +} diff --git a/source/gl_model.c b/source/gl_model.c new file mode 100644 index 0000000..443bb47 --- /dev/null +++ b/source/gl_model.c @@ -0,0 +1,2865 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +qmodel_t *loadmodel; +char loadname[32]; // for hunk tags + +void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer); +void Mod_LoadBrushModel (qmodel_t *mod, void *buffer); +void Mod_LoadAliasModel (qmodel_t *mod, void *buffer); +qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash); + +cvar_t external_ents = {"external_ents", "1", CVAR_ARCHIVE}; + +static byte *mod_novis; +static int mod_novis_capacity; + +static byte *mod_decompressed; +static int mod_decompressed_capacity; + +#define MAX_MOD_KNOWN 2048 /*johnfitz -- was 512 */ +qmodel_t mod_known[MAX_MOD_KNOWN]; +int mod_numknown; + +texture_t *r_notexture_mip; //johnfitz -- moved here from r_main.c +texture_t *r_notexture_mip2; //johnfitz -- used for non-lightmapped surfs with a missing texture + +/* +=============== +Mod_Init +=============== +*/ +void Mod_Init (void) +{ + Cvar_RegisterVariable (&gl_subdivide_size); + Cvar_RegisterVariable (&external_ents); + + //johnfitz -- create notexture miptex + r_notexture_mip = (texture_t *) Hunk_AllocName (sizeof(texture_t), "r_notexture_mip"); + strcpy (r_notexture_mip->name, "notexture"); + r_notexture_mip->height = r_notexture_mip->width = 32; + + r_notexture_mip2 = (texture_t *) Hunk_AllocName (sizeof(texture_t), "r_notexture_mip2"); + strcpy (r_notexture_mip2->name, "notexture2"); + r_notexture_mip2->height = r_notexture_mip2->width = 32; + //johnfitz +} + +/* +=============== +Mod_Extradata + +Caches the data if needed +=============== +*/ +void *Mod_Extradata (qmodel_t *mod) +{ + void *r; + + r = Cache_Check (&mod->cache); + if (r) + return r; + + Mod_LoadModel (mod, true); + + if (!mod->cache.data) + Sys_Error ("Mod_Extradata: caching failed"); + return mod->cache.data; +} + +/* +=============== +Mod_PointInLeaf +=============== +*/ +mleaf_t *Mod_PointInLeaf (vec3_t p, qmodel_t *model) +{ + mnode_t *node; + float d; + mplane_t *plane; + + if (!model || !model->nodes) + Sys_Error ("Mod_PointInLeaf: bad model"); + + node = model->nodes; + while (1) + { + if (node->contents < 0) + return (mleaf_t *)node; + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return NULL; // never reached +} + + +/* +=================== +Mod_DecompressVis +=================== +*/ +byte *Mod_DecompressVis (byte *in, qmodel_t *model) +{ + int c; + byte *out; + byte *outend; + int row; + + row = (model->numleafs+7)>>3; + if (mod_decompressed == NULL || row > mod_decompressed_capacity) + { + mod_decompressed_capacity = row; + mod_decompressed = (byte *) realloc (mod_decompressed, mod_decompressed_capacity); + if (!mod_decompressed) + Sys_Error ("Mod_DecompressVis: realloc() failed on %d bytes", mod_decompressed_capacity); + } + out = mod_decompressed; + outend = mod_decompressed + row; + + if (!in) + { // no vis info, so make all visible + while (row) + { + *out++ = 0xff; + row--; + } + return mod_decompressed; + } + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + if (out == outend) + { + if(!model->viswarn) { + model->viswarn = true; + Con_Warning("Mod_DecompressVis: output overrun on model \"%s\"\n", model->name); + } + return mod_decompressed; + } + *out++ = 0; + c--; + } + } while (out - mod_decompressed < row); + + return mod_decompressed; +} + +byte *Mod_LeafPVS (mleaf_t *leaf, qmodel_t *model) +{ + if (leaf == model->leafs) + return Mod_NoVisPVS (model); + return Mod_DecompressVis (leaf->compressed_vis, model); +} + +byte *Mod_NoVisPVS (qmodel_t *model) +{ + int pvsbytes; + + pvsbytes = (model->numleafs+7)>>3; + if (mod_novis == NULL || pvsbytes > mod_novis_capacity) + { + mod_novis_capacity = pvsbytes; + mod_novis = (byte *) realloc (mod_novis, mod_novis_capacity); + if (!mod_novis) + Sys_Error ("Mod_NoVisPVS: realloc() failed on %d bytes", mod_novis_capacity); + + memset(mod_novis, 0xff, mod_novis_capacity); + } + return mod_novis; +} + +/* +=================== +Mod_ClearAll +=================== +*/ +void Mod_ClearAll (void) +{ + int i; + qmodel_t *mod; + + for (i=0 , mod=mod_known ; itype != mod_alias) + { + mod->needload = true; + TexMgr_FreeTexturesForOwner (mod); //johnfitz + } +} + +void Mod_ResetAll (void) +{ + int i; + qmodel_t *mod; + + //ericw -- free alias model VBOs + GLMesh_DeleteVertexBuffers (); + + for (i=0 , mod=mod_known ; ineedload) //otherwise Mod_ClearAll() did it already + TexMgr_FreeTexturesForOwner (mod); + memset(mod, 0, sizeof(qmodel_t)); + } + mod_numknown = 0; +} + +/* +================== +Mod_FindName + +================== +*/ +qmodel_t *Mod_FindName (const char *name) +{ + int i; + qmodel_t *mod; + + if (!name[0]) + Sys_Error ("Mod_FindName: NULL name"); //johnfitz -- was "Mod_ForName" + +// +// search the currently loaded models +// + for (i=0 , mod=mod_known ; iname, name) ) + break; + + if (i == mod_numknown) + { + if (mod_numknown == MAX_MOD_KNOWN) + Sys_Error ("mod_numknown == MAX_MOD_KNOWN"); + q_strlcpy (mod->name, name, MAX_QPATH); + mod->needload = true; + mod_numknown++; + } + + return mod; +} + +/* +================== +Mod_TouchModel + +================== +*/ +void Mod_TouchModel (const char *name) +{ + qmodel_t *mod; + + mod = Mod_FindName (name); + + if (!mod->needload) + { + if (mod->type == mod_alias) + Cache_Check (&mod->cache); + } +} + +/* +================== +Mod_LoadModel + +Loads a model into the cache +================== +*/ +qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash) +{ + byte *buf; + byte stackbuf[1024]; // avoid dirtying the cache heap + int mod_type; + + if (!mod->needload) + { + if (mod->type == mod_alias) + { + if (Cache_Check (&mod->cache)) + return mod; + } + else + return mod; // not cached at all + } + +// +// because the world is so huge, load it one piece at a time +// + if (!crash) + { + + } + +// +// load the file +// + buf = COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf), & mod->path_id); + if (!buf) + { + if (crash) + Host_Error ("Mod_LoadModel: %s not found", mod->name); //johnfitz -- was "Mod_NumForName" + return NULL; + } + +// +// allocate a new model +// + COM_FileBase (mod->name, loadname, sizeof(loadname)); + + loadmodel = mod; + +// +// fill it in +// + +// call the apropriate loader + mod->needload = false; + + mod_type = (buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24)); + switch (mod_type) + { + case IDPOLYHEADER: + Mod_LoadAliasModel (mod, buf); + break; + + case IDSPRITEHEADER: + Mod_LoadSpriteModel (mod, buf); + break; + + default: + Mod_LoadBrushModel (mod, buf); + break; + } + + return mod; +} + +/* +================== +Mod_ForName + +Loads in a model for the given name +================== +*/ +qmodel_t *Mod_ForName (const char *name, qboolean crash) +{ + qmodel_t *mod; + + mod = Mod_FindName (name); + + return Mod_LoadModel (mod, crash); +} + + +/* +=============================================================================== + + BRUSHMODEL LOADING + +=============================================================================== +*/ + +byte *mod_base; + +/* +================= +Mod_CheckFullbrights -- johnfitz +================= +*/ +qboolean Mod_CheckFullbrights (byte *pixels, int count) +{ + int i; + for (i = 0; i < count; i++) + if (*pixels++ > 223) + return true; + return false; +} + +/* +================= +Mod_LoadTextures +================= +*/ +void Mod_LoadTextures (lump_t *l) +{ + int i, j, pixels, num, maxanim, altmax; + miptex_t *mt; + texture_t *tx, *tx2; + texture_t *anims[10]; + texture_t *altanims[10]; + dmiptexlump_t *m; +//johnfitz -- more variables + char texturename[64]; + int nummiptex; + src_offset_t offset; + int mark, fwidth, fheight; + char filename[MAX_OSPATH], filename2[MAX_OSPATH], mapname[MAX_OSPATH]; + byte *data; + extern byte *hunk_base; +//johnfitz + + //johnfitz -- don't return early if no textures; still need to create dummy texture + if (!l->filelen) + { + Con_Printf ("Mod_LoadTextures: no textures in bsp file\n"); + nummiptex = 0; + m = NULL; // avoid bogus compiler warning + } + else + { + m = (dmiptexlump_t *)(mod_base + l->fileofs); + m->nummiptex = LittleLong (m->nummiptex); + nummiptex = m->nummiptex; + } + //johnfitz + + loadmodel->numtextures = nummiptex + 2; //johnfitz -- need 2 dummy texture chains for missing textures + loadmodel->textures = (texture_t **) Hunk_AllocName (loadmodel->numtextures * sizeof(*loadmodel->textures) , loadname); + + for (i=0 ; idataofs[i] = LittleLong(m->dataofs[i]); + if (m->dataofs[i] == -1) + continue; + mt = (miptex_t *)((byte *)m + m->dataofs[i]); + mt->width = LittleLong (mt->width); + mt->height = LittleLong (mt->height); + for (j=0 ; joffsets[j] = LittleLong (mt->offsets[j]); + + if ( (mt->width & 15) || (mt->height & 15) ) + Sys_Error ("Texture %s is not 16 aligned", mt->name); + pixels = mt->width*mt->height/64*85; + tx = (texture_t *) Hunk_AllocName (sizeof(texture_t) +pixels, loadname ); + loadmodel->textures[i] = tx; + + memcpy (tx->name, mt->name, sizeof(tx->name)); + tx->width = mt->width; + tx->height = mt->height; + for (j=0 ; joffsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t); + // the pixels immediately follow the structures + + // ericw -- check for pixels extending past the end of the lump. + // appears in the wild; e.g. jam2_tronyn.bsp (func_mapjam2), + // kellbase1.bsp (quoth), and can lead to a segfault if we read past + // the end of the .bsp file buffer + if (((byte*)(mt+1) + pixels) > (mod_base + l->fileofs + l->filelen)) + { + Con_DPrintf("Texture %s extends past end of lump\n", mt->name); + pixels = q_max(0, (mod_base + l->fileofs + l->filelen) - (byte*)(mt+1)); + } + memcpy ( tx+1, mt+1, pixels); + + tx->update_warp = false; //johnfitz + tx->warpimage = NULL; //johnfitz + tx->fullbright = NULL; //johnfitz + + //johnfitz -- lots of changes + if (!isDedicated) //no texture uploading for dedicated server + { + if (!q_strncasecmp(tx->name,"sky",3)) //sky texture //also note -- was Q_strncmp, changed to match qbsp + Sky_LoadTexture (tx); + else if (tx->name[0] == '*') //warping texture + { + //external textures -- first look in "textures/mapname/" then look in "textures/" + mark = Hunk_LowMark(); + COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); + q_snprintf (filename, sizeof(filename), "textures/%s/#%s", mapname, tx->name+1); //this also replaces the '*' with a '#' + data = Image_LoadImage (filename, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename, sizeof(filename), "textures/#%s", tx->name+1); + data = Image_LoadImage (filename, &fwidth, &fheight); + } + + //now load whatever we found + if (data) //load external image + { + q_strlcpy (texturename, filename, sizeof(texturename)); + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, fwidth, fheight, + SRC_RGBA, data, filename, 0, TEXPREF_NONE); + } + else //use the texture from the bsp file + { + q_snprintf (texturename, sizeof(texturename), "%s:%s", loadmodel->name, tx->name); + offset = (src_offset_t)(mt+1) - (src_offset_t)mod_base; + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_NONE); + } + + //now create the warpimage, using dummy data from the hunk to create the initial image + Hunk_Alloc (gl_warpimagesize*gl_warpimagesize*4); //make sure hunk is big enough so we don't reach an illegal address + Hunk_FreeToLowMark (mark); + q_snprintf (texturename, sizeof(texturename), "%s_warp", texturename); + tx->warpimage = TexMgr_LoadImage (loadmodel, texturename, gl_warpimagesize, + gl_warpimagesize, SRC_RGBA, hunk_base, "", (src_offset_t)hunk_base, TEXPREF_NOPICMIP | TEXPREF_WARPIMAGE); + tx->update_warp = true; + } + else //regular texture + { + // ericw -- fence textures + int extraflags; + + extraflags = 0; + if (tx->name[0] == '{') + extraflags |= TEXPREF_ALPHA; + // ericw + + //external textures -- first look in "textures/mapname/" then look in "textures/" + mark = Hunk_LowMark (); + COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); + q_snprintf (filename, sizeof(filename), "textures/%s/%s", mapname, tx->name); + data = Image_LoadImage (filename, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename, sizeof(filename), "textures/%s", tx->name); + data = Image_LoadImage (filename, &fwidth, &fheight); + } + + //now load whatever we found + if (data) //load external image + { + tx->gltexture = TexMgr_LoadImage (loadmodel, filename, fwidth, fheight, + SRC_RGBA, data, filename, 0, TEXPREF_MIPMAP | extraflags ); + + //now try to load glow/luma image from the same place + Hunk_FreeToLowMark (mark); + q_snprintf (filename2, sizeof(filename2), "%s_glow", filename); + data = Image_LoadImage (filename2, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename2, sizeof(filename2), "%s_luma", filename); + data = Image_LoadImage (filename2, &fwidth, &fheight); + } + + if (data) + tx->fullbright = TexMgr_LoadImage (loadmodel, filename2, fwidth, fheight, + SRC_RGBA, data, filename, 0, TEXPREF_MIPMAP | extraflags ); + } + else //use the texture from the bsp file + { + q_snprintf (texturename, sizeof(texturename), "%s:%s", loadmodel->name, tx->name); + offset = (src_offset_t)(mt+1) - (src_offset_t)mod_base; + if (Mod_CheckFullbrights ((byte *)(tx+1), pixels)) + { + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | TEXPREF_NOBRIGHT | extraflags); + q_snprintf (texturename, sizeof(texturename), "%s:%s_glow", loadmodel->name, tx->name); + tx->fullbright = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | TEXPREF_FULLBRIGHT | extraflags); + } + else + { + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | extraflags); + } + } + Hunk_FreeToLowMark (mark); + } + } + //johnfitz + } + + //johnfitz -- last 2 slots in array should be filled with dummy textures + loadmodel->textures[loadmodel->numtextures-2] = r_notexture_mip; //for lightmapped surfs + loadmodel->textures[loadmodel->numtextures-1] = r_notexture_mip2; //for SURF_DRAWTILED surfs + +// +// sequence the animations +// + for (i=0 ; itextures[i]; + if (!tx || tx->name[0] != '+') + continue; + if (tx->anim_next) + continue; // allready sequenced + + // find the number of frames in the animation + memset (anims, 0, sizeof(anims)); + memset (altanims, 0, sizeof(altanims)); + + maxanim = tx->name[1]; + altmax = 0; + if (maxanim >= 'a' && maxanim <= 'z') + maxanim -= 'a' - 'A'; + if (maxanim >= '0' && maxanim <= '9') + { + maxanim -= '0'; + altmax = 0; + anims[maxanim] = tx; + maxanim++; + } + else if (maxanim >= 'A' && maxanim <= 'J') + { + altmax = maxanim - 'A'; + maxanim = 0; + altanims[altmax] = tx; + altmax++; + } + else + Sys_Error ("Bad animating texture %s", tx->name); + + for (j=i+1 ; jtextures[j]; + if (!tx2 || tx2->name[0] != '+') + continue; + if (strcmp (tx2->name+2, tx->name+2)) + continue; + + num = tx2->name[1]; + if (num >= 'a' && num <= 'z') + num -= 'a' - 'A'; + if (num >= '0' && num <= '9') + { + num -= '0'; + anims[num] = tx2; + if (num+1 > maxanim) + maxanim = num + 1; + } + else if (num >= 'A' && num <= 'J') + { + num = num - 'A'; + altanims[num] = tx2; + if (num+1 > altmax) + altmax = num+1; + } + else + Sys_Error ("Bad animating texture %s", tx->name); + } + +#define ANIM_CYCLE 2 + // link them all together + for (j=0 ; jname); + tx2->anim_total = maxanim * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j+1) * ANIM_CYCLE; + tx2->anim_next = anims[ (j+1)%maxanim ]; + if (altmax) + tx2->alternate_anims = altanims[0]; + } + for (j=0 ; jname); + tx2->anim_total = altmax * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j+1) * ANIM_CYCLE; + tx2->anim_next = altanims[ (j+1)%altmax ]; + if (maxanim) + tx2->alternate_anims = anims[0]; + } + } +} + +/* +================= +Mod_LoadLighting -- johnfitz -- replaced with lit support code via lordhavoc +================= +*/ +void Mod_LoadLighting (lump_t *l) +{ + int i, mark; + byte *in, *out, *data; + byte d; + char litfilename[MAX_OSPATH]; + unsigned int path_id; + + loadmodel->lightdata = NULL; + // LordHavoc: check for a .lit file + q_strlcpy(litfilename, loadmodel->name, sizeof(litfilename)); + COM_StripExtension(litfilename, litfilename, sizeof(litfilename)); + q_strlcat(litfilename, ".lit", sizeof(litfilename)); + mark = Hunk_LowMark(); + data = (byte*) COM_LoadHunkFile (litfilename, &path_id); + if (data) + { + // use lit file only from the same gamedir as the map + // itself or from a searchpath with higher priority. + if (path_id < loadmodel->path_id) + { + Hunk_FreeToLowMark(mark); + Con_DPrintf("ignored %s from a gamedir with lower priority\n", litfilename); + } + else + if (data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + Con_DPrintf2("%s loaded\n", litfilename); + loadmodel->lightdata = data + 8; + return; + } + else + { + Hunk_FreeToLowMark(mark); + Con_Printf("Unknown .lit file version (%d)\n", i); + } + } + else + { + Hunk_FreeToLowMark(mark); + Con_Printf("Corrupt .lit file (old version?), ignoring\n"); + } + } + // LordHavoc: no .lit found, expand the white lighting data to color + if (!l->filelen) + return; + loadmodel->lightdata = (byte *) Hunk_AllocName ( l->filelen*3, litfilename); + in = loadmodel->lightdata + l->filelen*2; // place the file at the end, so it will not be overwritten until the very last write + out = loadmodel->lightdata; + memcpy (in, mod_base + l->fileofs, l->filelen); + for (i = 0;i < l->filelen;i++) + { + d = *in++; + *out++ = d; + *out++ = d; + *out++ = d; + } +} + + +/* +================= +Mod_LoadVisibility +================= +*/ +void Mod_LoadVisibility (lump_t *l) +{ + loadmodel->viswarn = false; + if (!l->filelen) + { + loadmodel->visdata = NULL; + return; + } + loadmodel->visdata = (byte *) Hunk_AllocName ( l->filelen, loadname); + memcpy (loadmodel->visdata, mod_base + l->fileofs, l->filelen); +} + + +/* +================= +Mod_LoadEntities +================= +*/ +void Mod_LoadEntities (lump_t *l) +{ + char entfilename[MAX_QPATH]; + char *ents; + int mark; + unsigned int path_id; + + if (! external_ents.value) + goto _load_embedded; + + q_strlcpy(entfilename, loadmodel->name, sizeof(entfilename)); + COM_StripExtension(entfilename, entfilename, sizeof(entfilename)); + q_strlcat(entfilename, ".ent", sizeof(entfilename)); + Con_DPrintf2("trying to load %s\n", entfilename); + mark = Hunk_LowMark(); + ents = (char *) COM_LoadHunkFile (entfilename, &path_id); + if (ents) + { + // use ent file only from the same gamedir as the map + // itself or from a searchpath with higher priority. + if (path_id < loadmodel->path_id) + { + Hunk_FreeToLowMark(mark); + Con_DPrintf("ignored %s from a gamedir with lower priority\n", entfilename); + } + else + { + loadmodel->entities = ents; + Con_DPrintf("Loaded external entity file %s\n", entfilename); + return; + } + } + +_load_embedded: + if (!l->filelen) + { + loadmodel->entities = NULL; + return; + } + loadmodel->entities = (char *) Hunk_AllocName ( l->filelen, loadname); + memcpy (loadmodel->entities, mod_base + l->fileofs, l->filelen); +} + + +/* +================= +Mod_LoadVertexes +================= +*/ +void Mod_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)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mvertex_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->vertexes = out; + loadmodel->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]); + } +} + +/* +================= +Mod_LoadEdges +================= +*/ +void Mod_LoadEdges (lump_t *l, int bsp2) +{ + medge_t *out; + int i, count; + + if (bsp2) + { + dledge_t *in = (dledge_t *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadname); + + loadmodel->edges = out; + loadmodel->numedges = count; + + for (i=0 ; iv[0] = LittleLong(in->v[0]); + out->v[1] = LittleLong(in->v[1]); + } + } + else + { + dsedge_t *in = (dsedge_t *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadname); + + loadmodel->edges = out; + loadmodel->numedges = count; + + for (i=0 ; iv[0] = (unsigned short)LittleShort(in->v[0]); + out->v[1] = (unsigned short)LittleShort(in->v[1]); + } + } +} + +/* +================= +Mod_LoadTexinfo +================= +*/ +void Mod_LoadTexinfo (lump_t *l) +{ + texinfo_t *in; + mtexinfo_t *out; + int i, j, count, miptex; + float len1, len2; + int missing = 0; //johnfitz + + in = (texinfo_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mtexinfo_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->texinfo = out; + loadmodel->numtexinfo = count; + + for (i=0 ; ivecs[0][j] = LittleFloat (in->vecs[0][j]); + out->vecs[1][j] = LittleFloat (in->vecs[1][j]); + } + len1 = VectorLength (out->vecs[0]); + len2 = VectorLength (out->vecs[1]); + len1 = (len1 + len2)/2; + if (len1 < 0.32) + out->mipadjust = 4; + else if (len1 < 0.49) + out->mipadjust = 3; + else if (len1 < 0.99) + out->mipadjust = 2; + else + out->mipadjust = 1; +#if 0 + if (len1 + len2 < 0.001) + out->mipadjust = 1; // don't crash + else + out->mipadjust = 1 / floor((len1+len2)/2 + 0.1); +#endif + + miptex = LittleLong (in->miptex); + out->flags = LittleLong (in->flags); + + //johnfitz -- rewrote this section + if (miptex >= loadmodel->numtextures-1 || !loadmodel->textures[miptex]) + { + if (out->flags & TEX_SPECIAL) + out->texture = loadmodel->textures[loadmodel->numtextures-1]; + else + out->texture = loadmodel->textures[loadmodel->numtextures-2]; + out->flags |= TEX_MISSING; + missing++; + } + else + { + out->texture = loadmodel->textures[miptex]; + } + //johnfitz + } + + //johnfitz: report missing textures + if (missing && loadmodel->numtextures > 1) + Con_Printf ("Mod_LoadTexinfo: %d texture(s) missing from BSP file\n", missing); + //johnfitz +} + +/* +================ +CalcSurfaceExtents + +Fills in s->texturemins[] and s->extents[] +================ +*/ +void CalcSurfaceExtents (msurface_t *s) +{ + float mins[2], maxs[2], val; + int i,j, e; + mvertex_t *v; + mtexinfo_t *tex; + int bmins[2], bmaxs[2]; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = s->texinfo; + + for (i=0 ; inumedges ; i++) + { + e = loadmodel->surfedges[s->firstedge+i]; + if (e >= 0) + v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else + v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + + for (j=0 ; j<2 ; j++) + { + /* The following calculation is sensitive to floating-point + * precision. It needs to produce the same result that the + * light compiler does, because R_BuildLightMap uses surf-> + * extents to know the width/height of a surface's lightmap, + * and incorrect rounding here manifests itself as patches + * of "corrupted" looking lightmaps. + * Most light compilers are win32 executables, so they use + * x87 floating point. This means the multiplies and adds + * are done at 80-bit precision, and the result is rounded + * down to 32-bits and stored in val. + * Adding the casts to double seems to be good enough to fix + * lighting glitches when Quakespasm is compiled as x86_64 + * and using SSE2 floating-point. A potential trouble spot + * is the hallway at the beginning of mfxsp17. -- ericw + */ + val = ((double)v->position[0] * (double)tex->vecs[j][0]) + + ((double)v->position[1] * (double)tex->vecs[j][1]) + + ((double)v->position[2] * (double)tex->vecs[j][2]) + + (double)tex->vecs[j][3]; + + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + bmins[i] = floor(mins[i]/16); + bmaxs[i] = ceil(maxs[i]/16); + + s->texturemins[i] = bmins[i] * 16; + s->extents[i] = (bmaxs[i] - bmins[i]) * 16; + + if ( !(tex->flags & TEX_SPECIAL) && s->extents[i] > 2000) //johnfitz -- was 512 in glquake, 256 in winquake + Sys_Error ("Bad surface extents"); + } +} + +/* +================ +Mod_PolyForUnlitSurface -- johnfitz -- creates polys for unlightmapped surfaces (sky and water) + +TODO: merge this into BuildSurfaceDisplayList? +================ +*/ +void Mod_PolyForUnlitSurface (msurface_t *fa) +{ + vec3_t verts[64]; + int numverts, i, lindex; + float *vec; + glpoly_t *poly; + float texscale; + + if (fa->flags & (SURF_DRAWTURB | SURF_DRAWSKY)) + texscale = (1.0/128.0); //warp animation repeats every 128 + else + texscale = (1.0/32.0); //to match r_notexture_mip + + // convert edges back to a normal polygon + numverts = 0; + for (i=0 ; inumedges ; i++) + { + lindex = loadmodel->surfedges[fa->firstedge + i]; + + if (lindex > 0) + vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position; + else + vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; + VectorCopy (vec, verts[numverts]); + numverts++; + } + + //create the poly + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = NULL; + fa->polys = poly; + poly->numverts = numverts; + for (i=0, vec=(float *)verts; iverts[i]); + poly->verts[i][3] = DotProduct(vec, fa->texinfo->vecs[0]) * texscale; + poly->verts[i][4] = DotProduct(vec, fa->texinfo->vecs[1]) * texscale; + } +} + +/* +================= +Mod_CalcSurfaceBounds -- johnfitz -- calculate bounding box for per-surface frustum culling +================= +*/ +void Mod_CalcSurfaceBounds (msurface_t *s) +{ + int i, e; + mvertex_t *v; + + s->mins[0] = s->mins[1] = s->mins[2] = 9999; + s->maxs[0] = s->maxs[1] = s->maxs[2] = -9999; + + for (i=0 ; inumedges ; i++) + { + e = loadmodel->surfedges[s->firstedge+i]; + if (e >= 0) + v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else + v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + + if (s->mins[0] > v->position[0]) + s->mins[0] = v->position[0]; + if (s->mins[1] > v->position[1]) + s->mins[1] = v->position[1]; + if (s->mins[2] > v->position[2]) + s->mins[2] = v->position[2]; + + if (s->maxs[0] < v->position[0]) + s->maxs[0] = v->position[0]; + if (s->maxs[1] < v->position[1]) + s->maxs[1] = v->position[1]; + if (s->maxs[2] < v->position[2]) + s->maxs[2] = v->position[2]; + } +} + +/* +================= +Mod_LoadFaces +================= +*/ +void Mod_LoadFaces (lump_t *l, qboolean bsp2) +{ + dsface_t *ins; + dlface_t *inl; + msurface_t *out; + int i, count, surfnum, lofs; + int planenum, side, texinfon; + + if (bsp2) + { + ins = NULL; + inl = (dlface_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*inl)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*inl); + } + else + { + ins = (dsface_t *)(mod_base + l->fileofs); + inl = NULL; + if (l->filelen % sizeof(*ins)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*ins); + } + out = (msurface_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767 && !bsp2) + Con_DWarning ("%i faces exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->surfaces = out; + loadmodel->numsurfaces = count; + + for (surfnum=0 ; surfnumfirstedge = LittleLong(inl->firstedge); + out->numedges = LittleLong(inl->numedges); + planenum = LittleLong(inl->planenum); + side = LittleLong(inl->side); + texinfon = LittleLong (inl->texinfo); + for (i=0 ; istyles[i] = inl->styles[i]; + lofs = LittleLong(inl->lightofs); + inl++; + } + else + { + out->firstedge = LittleLong(ins->firstedge); + out->numedges = LittleShort(ins->numedges); + planenum = LittleShort(ins->planenum); + side = LittleShort(ins->side); + texinfon = LittleShort (ins->texinfo); + for (i=0 ; istyles[i] = ins->styles[i]; + lofs = LittleLong(ins->lightofs); + ins++; + } + + out->flags = 0; + + if (side) + out->flags |= SURF_PLANEBACK; + + out->plane = loadmodel->planes + planenum; + + out->texinfo = loadmodel->texinfo + texinfon; + + CalcSurfaceExtents (out); + + Mod_CalcSurfaceBounds (out); //johnfitz -- for per-surface frustum culling + + // lighting info + if (lofs == -1) + out->samples = NULL; + else + out->samples = loadmodel->lightdata + (lofs * 3); //johnfitz -- lit support via lordhavoc (was "+ i") + + //johnfitz -- this section rewritten + if (!q_strncasecmp(out->texinfo->texture->name,"sky",3)) // sky surface //also note -- was Q_strncmp, changed to match qbsp + { + out->flags |= (SURF_DRAWSKY | SURF_DRAWTILED); + Mod_PolyForUnlitSurface (out); //no more subdivision + } + else if (out->texinfo->texture->name[0] == '*') // warp surface + { + out->flags |= (SURF_DRAWTURB | SURF_DRAWTILED); + + // detect special liquid types + if (!strncmp (out->texinfo->texture->name, "*lava", 5)) + out->flags |= SURF_DRAWLAVA; + else if (!strncmp (out->texinfo->texture->name, "*slime", 6)) + out->flags |= SURF_DRAWSLIME; + else if (!strncmp (out->texinfo->texture->name, "*tele", 5)) + out->flags |= SURF_DRAWTELE; + else out->flags |= SURF_DRAWWATER; + + Mod_PolyForUnlitSurface (out); + GL_SubdivideSurface (out); + } + else if (out->texinfo->texture->name[0] == '{') // ericw -- fence textures + { + out->flags |= SURF_DRAWFENCE; + } + else if (out->texinfo->flags & TEX_MISSING) // texture is missing from bsp + { + if (out->samples) //lightmapped + { + out->flags |= SURF_NOTEXTURE; + } + else // not lightmapped + { + out->flags |= (SURF_NOTEXTURE | SURF_DRAWTILED); + Mod_PolyForUnlitSurface (out); + } + } + //johnfitz + } +} + + +/* +================= +Mod_SetParent +================= +*/ +void Mod_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents < 0) + return; + Mod_SetParent (node->children[0], node); + Mod_SetParent (node->children[1], node); +} + +/* +================= +Mod_LoadNodes +================= +*/ +void Mod_LoadNodes_S (lump_t *l) +{ + int i, j, count, p; + dsnode_t *in; + mnode_t *out; + + in = (dsnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767) + Con_DWarning ("%i nodes exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = (unsigned short)LittleShort (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = (unsigned short)LittleShort (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = (unsigned short)LittleShort(in->children[j]); + if (p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 65535 - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +void Mod_LoadNodes_L1 (lump_t *l) +{ + int i, j, count, p; + dl1node_t *in; + mnode_t *out; + + in = (dl1node_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("Mod_LoadNodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (mnode_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = LittleLong (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = LittleLong (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = LittleLong(in->children[j]); + if (p >= 0 && p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 0xffffffff - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p >= 0 && p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +void Mod_LoadNodes_L2 (lump_t *l) +{ + int i, j, count, p; + dl2node_t *in; + mnode_t *out; + + in = (dl2node_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("Mod_LoadNodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (mnode_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleFloat (in->mins[j]); + out->minmaxs[3+j] = LittleFloat (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = LittleLong (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = LittleLong (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = LittleLong(in->children[j]); + if (p > 0 && p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 0xffffffff - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p >= 0 && p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +void Mod_LoadNodes (lump_t *l, int bsp2) +{ + if (bsp2 == 2) + Mod_LoadNodes_L2(l); + else if (bsp2) + Mod_LoadNodes_L1(l); + else + Mod_LoadNodes_S(l); + + Mod_SetParent (loadmodel->nodes, NULL); // sets nodes and leafs +} + +void Mod_ProcessLeafs_S (dsleaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + count = filelen / sizeof(*in); + out = (mleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz + if (count > 32767) + Host_Error ("Mod_LoadLeafs: %i leafs exceeds limit of 32767.\n", count); + //johnfitz + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + (unsigned short)LittleShort(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = (unsigned short)LittleShort(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +void Mod_ProcessLeafs_L1 (dl1leaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + + count = filelen / sizeof(*in); + + out = (mleaf_t *) Hunk_AllocName (count * sizeof(*out), loadname); + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + LittleLong(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = LittleLong(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +void Mod_ProcessLeafs_L2 (dl2leaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + + count = filelen / sizeof(*in); + + out = (mleaf_t *) Hunk_AllocName (count * sizeof(*out), loadname); + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleFloat (in->mins[j]); + out->minmaxs[3+j] = LittleFloat (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + LittleLong(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = LittleLong(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +/* +================= +Mod_LoadLeafs +================= +*/ +void Mod_LoadLeafs (lump_t *l, int bsp2) +{ + void *in = (void *)(mod_base + l->fileofs); + + if (bsp2 == 2) + Mod_ProcessLeafs_L2 ((dl2leaf_t *)in, l->filelen); + else if (bsp2) + Mod_ProcessLeafs_L1 ((dl1leaf_t *)in, l->filelen); + else + Mod_ProcessLeafs_S ((dsleaf_t *) in, l->filelen); +} + +/* +================= +Mod_LoadClipnodes +================= +*/ +void Mod_LoadClipnodes (lump_t *l, qboolean bsp2) +{ + dsclipnode_t *ins; + dlclipnode_t *inl; + + mclipnode_t *out; //johnfitz -- was dclipnode_t + int i, count; + hull_t *hull; + + if (bsp2) + { + ins = NULL; + inl = (dlclipnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*inl)) + Sys_Error ("Mod_LoadClipnodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*inl); + } + else + { + ins = (dsclipnode_t *)(mod_base + l->fileofs); + inl = NULL; + if (l->filelen % sizeof(*ins)) + Sys_Error ("Mod_LoadClipnodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*ins); + } + out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn about exceeding old limits + if (count > 32767 && !bsp2) + Con_DWarning ("%i clipnodes exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->clipnodes = out; + loadmodel->numclipnodes = count; + + hull = &loadmodel->hulls[1]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + hull->clip_mins[0] = -16; + hull->clip_mins[1] = -16; + hull->clip_mins[2] = -24; + hull->clip_maxs[0] = 16; + hull->clip_maxs[1] = 16; + hull->clip_maxs[2] = 32; + + hull = &loadmodel->hulls[2]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + hull->clip_mins[0] = -32; + hull->clip_mins[1] = -32; + hull->clip_mins[2] = -24; + hull->clip_maxs[0] = 32; + hull->clip_maxs[1] = 32; + hull->clip_maxs[2] = 64; + + if (bsp2) + { + for (i=0 ; iplanenum = LittleLong(inl->planenum); + + //johnfitz -- bounds check + if (out->planenum < 0 || out->planenum >= loadmodel->numplanes) + Host_Error ("Mod_LoadClipnodes: planenum out of bounds"); + //johnfitz + + out->children[0] = LittleLong(inl->children[0]); + out->children[1] = LittleLong(inl->children[1]); + //Spike: FIXME: bounds check + } + } + else + { + for (i=0 ; iplanenum = LittleLong(ins->planenum); + + //johnfitz -- bounds check + if (out->planenum < 0 || out->planenum >= loadmodel->numplanes) + Host_Error ("Mod_LoadClipnodes: planenum out of bounds"); + //johnfitz + + //johnfitz -- support clipnodes > 32k + out->children[0] = (unsigned short)LittleShort(ins->children[0]); + out->children[1] = (unsigned short)LittleShort(ins->children[1]); + + if (out->children[0] >= count) + out->children[0] -= 65536; + if (out->children[1] >= count) + out->children[1] -= 65536; + //johnfitz + } + } +} + +/* +================= +Mod_MakeHull0 + +Duplicate the drawing hull structure as a clipping hull +================= +*/ +void Mod_MakeHull0 (void) +{ + mnode_t *in, *child; + mclipnode_t *out; //johnfitz -- was dclipnode_t + int i, j, count; + hull_t *hull; + + hull = &loadmodel->hulls[0]; + + in = loadmodel->nodes; + count = loadmodel->numnodes; + out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + + for (i=0 ; iplanenum = in->plane - loadmodel->planes; + for (j=0 ; j<2 ; j++) + { + child = in->children[j]; + if (child->contents < 0) + out->children[j] = child->contents; + else + out->children[j] = child - loadmodel->nodes; + } + } +} + +/* +================= +Mod_LoadMarksurfaces +================= +*/ +void Mod_LoadMarksurfaces (lump_t *l, int bsp2) +{ + int i, j, count; + msurface_t **out; + if (bsp2) + { + unsigned int *in = (unsigned int *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (msurface_t **)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->marksurfaces = out; + loadmodel->nummarksurfaces = count; + + for (i=0 ; i= loadmodel->numsurfaces) + Host_Error ("Mod_LoadMarksurfaces: bad surface number"); + out[i] = loadmodel->surfaces + j; + } + } + else + { + short *in = (short *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (msurface_t **)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->marksurfaces = out; + loadmodel->nummarksurfaces = count; + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767) + Con_DWarning ("%i marksurfaces exceeds standard limit of 32767.\n", count); + //johnfitz + + for (i=0 ; i= loadmodel->numsurfaces) + Sys_Error ("Mod_LoadMarksurfaces: bad surface number"); + out[i] = loadmodel->surfaces + j; + } + } +} + +/* +================= +Mod_LoadSurfedges +================= +*/ +void Mod_LoadSurfedges (lump_t *l) +{ + int i, count; + int *in, *out; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->surfedges = out; + loadmodel->numsurfedges = count; + + for (i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mplane_t *) Hunk_AllocName ( count*2*sizeof(*out), loadname); + + loadmodel->planes = out; + loadmodel->numplanes = count; + + for (i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = LittleLong (in->type); + out->signbits = bits; + } +} + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds (vec3_t mins, vec3_t maxs) +{ + int i; + vec3_t corner; + + for (i=0 ; i<3 ; i++) + { + corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); + } + + return VectorLength (corner); +} + +/* +================= +Mod_LoadSubmodels +================= +*/ +void Mod_LoadSubmodels (lump_t *l) +{ + dmodel_t *in; + dmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (dmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->submodels = out; + loadmodel->numsubmodels = count; + + for (i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + out->origin[j] = LittleFloat (in->origin[j]); + } + for (j=0 ; jheadnode[j] = LittleLong (in->headnode[j]); + out->visleafs = LittleLong (in->visleafs); + out->firstface = LittleLong (in->firstface); + out->numfaces = LittleLong (in->numfaces); + } + + // johnfitz -- check world visleafs -- adapted from bjp + out = loadmodel->submodels; + + if (out->visleafs > 8192) + Con_DWarning ("%i visleafs exceeds standard limit of 8192.\n", out->visleafs); + //johnfitz +} + +/* +================= +Mod_BoundsFromClipNode -- johnfitz + +update the model's clipmins and clipmaxs based on each node's plane. + +This works because of the way brushes are expanded in hull generation. +Each brush will include all six axial planes, which bound that brush. +Therefore, the bounding box of the hull can be constructed entirely +from axial planes found in the clipnodes for that hull. +================= +*/ +void Mod_BoundsFromClipNode (qmodel_t *mod, int hull, int nodenum) +{ + mplane_t *plane; + mclipnode_t *node; + + if (nodenum < 0) + return; //hit a leafnode + + node = &mod->clipnodes[nodenum]; + plane = mod->hulls[hull].planes + node->planenum; + switch (plane->type) + { + + case PLANE_X: + if (plane->signbits == 1) + mod->clipmins[0] = q_min(mod->clipmins[0], -plane->dist - mod->hulls[hull].clip_mins[0]); + else + mod->clipmaxs[0] = q_max(mod->clipmaxs[0], plane->dist - mod->hulls[hull].clip_maxs[0]); + break; + case PLANE_Y: + if (plane->signbits == 2) + mod->clipmins[1] = q_min(mod->clipmins[1], -plane->dist - mod->hulls[hull].clip_mins[1]); + else + mod->clipmaxs[1] = q_max(mod->clipmaxs[1], plane->dist - mod->hulls[hull].clip_maxs[1]); + break; + case PLANE_Z: + if (plane->signbits == 4) + mod->clipmins[2] = q_min(mod->clipmins[2], -plane->dist - mod->hulls[hull].clip_mins[2]); + else + mod->clipmaxs[2] = q_max(mod->clipmaxs[2], plane->dist - mod->hulls[hull].clip_maxs[2]); + break; + default: + //skip nonaxial planes; don't need them + break; + } + + Mod_BoundsFromClipNode (mod, hull, node->children[0]); + Mod_BoundsFromClipNode (mod, hull, node->children[1]); +} + +/* +================= +Mod_LoadBrushModel +================= +*/ +void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) +{ + int i, j; + int bsp2; + dheader_t *header; + dmodel_t *bm; + float radius; //johnfitz + + loadmodel->type = mod_brush; + + header = (dheader_t *)buffer; + + mod->bspversion = LittleLong (header->version); + + switch(mod->bspversion) + { + case BSPVERSION: + bsp2 = false; + break; + case BSP2VERSION_2PSB: + bsp2 = 1; //first iteration + break; + case BSP2VERSION_BSP2: + bsp2 = 2; //sanitised revision + break; + default: + Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, mod->bspversion, BSPVERSION); + break; + } + +// swap all the lumps + mod_base = (byte *)header; + + for (i = 0; i < (int) sizeof(dheader_t) / 4; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + +// load into heap + + Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]); + Mod_LoadEdges (&header->lumps[LUMP_EDGES], bsp2); + Mod_LoadSurfedges (&header->lumps[LUMP_SURFEDGES]); + Mod_LoadTextures (&header->lumps[LUMP_TEXTURES]); + Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]); + Mod_LoadPlanes (&header->lumps[LUMP_PLANES]); + Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]); + Mod_LoadFaces (&header->lumps[LUMP_FACES], bsp2); + Mod_LoadMarksurfaces (&header->lumps[LUMP_MARKSURFACES], bsp2); + Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]); + Mod_LoadLeafs (&header->lumps[LUMP_LEAFS], bsp2); + Mod_LoadNodes (&header->lumps[LUMP_NODES], bsp2); + Mod_LoadClipnodes (&header->lumps[LUMP_CLIPNODES], bsp2); + Mod_LoadEntities (&header->lumps[LUMP_ENTITIES]); + Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]); + + Mod_MakeHull0 (); + + mod->numframes = 2; // regular and alternate animation + +// +// set up the submodels (FIXME: this is confusing) +// + + // johnfitz -- okay, so that i stop getting confused every time i look at this loop, here's how it works: + // we're looping through the submodels starting at 0. Submodel 0 is the main model, so we don't have to + // worry about clobbering data the first time through, since it's the same data. At the end of the loop, + // we create a new copy of the data to use the next time through. + for (i=0 ; inumsubmodels ; i++) + { + bm = &mod->submodels[i]; + + mod->hulls[0].firstclipnode = bm->headnode[0]; + for (j=1 ; jhulls[j].firstclipnode = bm->headnode[j]; + mod->hulls[j].lastclipnode = mod->numclipnodes-1; + } + + mod->firstmodelsurface = bm->firstface; + mod->nummodelsurfaces = bm->numfaces; + + VectorCopy (bm->maxs, mod->maxs); + VectorCopy (bm->mins, mod->mins); + + //johnfitz -- calculate rotate bounds and yaw bounds + radius = RadiusFromBounds (mod->mins, mod->maxs); + mod->rmaxs[0] = mod->rmaxs[1] = mod->rmaxs[2] = mod->ymaxs[0] = mod->ymaxs[1] = mod->ymaxs[2] = radius; + mod->rmins[0] = mod->rmins[1] = mod->rmins[2] = mod->ymins[0] = mod->ymins[1] = mod->ymins[2] = -radius; + //johnfitz + + //johnfitz -- correct physics cullboxes so that outlying clip brushes on doors and stuff are handled right + if (i > 0 || strcmp(mod->name, sv.modelname) != 0) //skip submodel 0 of sv.worldmodel, which is the actual world + { + // start with the hull0 bounds + VectorCopy (mod->maxs, mod->clipmaxs); + VectorCopy (mod->mins, mod->clipmins); + + // process hull1 (we don't need to process hull2 becuase there's + // no such thing as a brush that appears in hull2 but not hull1) + //Mod_BoundsFromClipNode (mod, 1, mod->hulls[1].firstclipnode); // (disabled for now becuase it fucks up on rotating models) + } + //johnfitz + + mod->numleafs = bm->visleafs; + + if (i < mod->numsubmodels-1) + { // duplicate the basic information + char name[10]; + + sprintf (name, "*%i", i+1); + loadmodel = Mod_FindName (name); + *loadmodel = *mod; + strcpy (loadmodel->name, name); + mod = loadmodel; + } + } +} + +/* +============================================================================== + +ALIAS MODELS + +============================================================================== +*/ + +aliashdr_t *pheader; + +stvert_t stverts[MAXALIASVERTS]; +mtriangle_t triangles[MAXALIASTRIS]; + +// a pose is a single set of vertexes. a frame may be +// an animating sequence of poses +trivertx_t *poseverts[MAXALIASFRAMES]; +int posenum; + +byte **player_8bit_texels_tbl; +byte *player_8bit_texels; + +/* +================= +Mod_LoadAliasFrame +================= +*/ +void * Mod_LoadAliasFrame (void * pin, maliasframedesc_t *frame) +{ + trivertx_t *pinframe; + int i; + daliasframe_t *pdaliasframe; + + pdaliasframe = (daliasframe_t *)pin; + + strcpy (frame->name, pdaliasframe->name); + frame->firstpose = posenum; + frame->numposes = 1; + + for (i=0 ; i<3 ; i++) + { + // these are byte values, so we don't have to worry about + // endianness + frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; + frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; + } + + + pinframe = (trivertx_t *)(pdaliasframe + 1); + + poseverts[posenum] = pinframe; + posenum++; + + pinframe += pheader->numverts; + + return (void *)pinframe; +} + + +/* +================= +Mod_LoadAliasGroup +================= +*/ +void *Mod_LoadAliasGroup (void * pin, maliasframedesc_t *frame) +{ + daliasgroup_t *pingroup; + int i, numframes; + daliasinterval_t *pin_intervals; + void *ptemp; + + pingroup = (daliasgroup_t *)pin; + + numframes = LittleLong (pingroup->numframes); + + frame->firstpose = posenum; + frame->numposes = numframes; + + for (i=0 ; i<3 ; i++) + { + // these are byte values, so we don't have to worry about endianness + frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; + frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; + } + + + pin_intervals = (daliasinterval_t *)(pingroup + 1); + + frame->interval = LittleFloat (pin_intervals->interval); + + pin_intervals += numframes; + + ptemp = (void *)pin_intervals; + + for (i=0 ; inumverts; + } + + return ptemp; +} + +//========================================================= + + +/* +================= +Mod_FloodFillSkin + +Fill background pixels so mipmapping doesn't have haloes - Ed +================= +*/ + +typedef struct +{ + short x, y; +} floodfill_t; + +// must be a power of 2 +#define FLOODFILL_FIFO_SIZE 0x1000 +#define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1) + +#define FLOODFILL_STEP( off, dx, dy ) \ +do { \ + if (pos[off] == fillcolor) \ + { \ + pos[off] = 255; \ + fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \ + inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \ + } \ + else if (pos[off] != 255) fdc = pos[off]; \ +} while (0) + +void Mod_FloodFillSkin( byte *skin, int skinwidth, int skinheight ) +{ + byte fillcolor = *skin; // assume this is the pixel to fill + floodfill_t fifo[FLOODFILL_FIFO_SIZE]; + int inpt = 0, outpt = 0; + int filledcolor = -1; + int i; + + if (filledcolor == -1) + { + filledcolor = 0; + // attempt to find opaque black + for (i = 0; i < 256; ++i) + if (d_8to24table[i] == (255 << 0)) // alpha 1.0 + { + filledcolor = i; + break; + } + } + + // can't fill to filled color or to transparent color (used as visited marker) + if ((fillcolor == filledcolor) || (fillcolor == 255)) + { + //printf( "not filling skin from %d to %d\n", fillcolor, filledcolor ); + return; + } + + fifo[inpt].x = 0, fifo[inpt].y = 0; + inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; + + while (outpt != inpt) + { + int x = fifo[outpt].x, y = fifo[outpt].y; + int fdc = filledcolor; + byte *pos = &skin[x + skinwidth * y]; + + outpt = (outpt + 1) & FLOODFILL_FIFO_MASK; + + if (x > 0) FLOODFILL_STEP( -1, -1, 0 ); + if (x < skinwidth - 1) FLOODFILL_STEP( 1, 1, 0 ); + if (y > 0) FLOODFILL_STEP( -skinwidth, 0, -1 ); + if (y < skinheight - 1) FLOODFILL_STEP( skinwidth, 0, 1 ); + skin[x + skinwidth * y] = fdc; + } +} + +/* +=============== +Mod_LoadAllSkins +=============== +*/ +void *Mod_LoadAllSkins (int numskins, daliasskintype_t *pskintype) +{ + int i, j, k, size, groupskins; + char name[MAX_QPATH]; + byte *skin, *texels; + daliasskingroup_t *pinskingroup; + daliasskininterval_t *pinskinintervals; + char fbr_mask_name[MAX_QPATH]; //johnfitz -- added for fullbright support + src_offset_t offset; //johnfitz + unsigned int texflags = TEXPREF_PAD; + + skin = (byte *)(pskintype + 1); + + if (numskins < 1 || numskins > MAX_SKINS) + Sys_Error ("Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins); + + size = pheader->skinwidth * pheader->skinheight; + + if (loadmodel->flags & MF_HOLEY) + texflags |= TEXPREF_ALPHA; + + for (i=0 ; itype == ALIAS_SKIN_SINGLE) + { + Mod_FloodFillSkin( skin, pheader->skinwidth, pheader->skinheight ); + + // save 8 bit texels for the player model to remap + texels = (byte *) Hunk_AllocName(size, loadname); + pheader->texels[i] = texels - (byte *)pheader; + memcpy (texels, (byte *)(pskintype + 1), size); + + //johnfitz -- rewritten + q_snprintf (name, sizeof(name), "%s:frame%i", loadmodel->name, i); + offset = (src_offset_t)(pskintype+1) - (src_offset_t)mod_base; + if (Mod_CheckFullbrights ((byte *)(pskintype+1), size)) + { + pheader->gltextures[i][0] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags | TEXPREF_NOBRIGHT); + q_snprintf (fbr_mask_name, sizeof(fbr_mask_name), "%s:frame%i_glow", loadmodel->name, i); + pheader->fbtextures[i][0] = TexMgr_LoadImage (loadmodel, fbr_mask_name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags | TEXPREF_FULLBRIGHT); + } + else + { + pheader->gltextures[i][0] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags); + pheader->fbtextures[i][0] = NULL; + } + + pheader->gltextures[i][3] = pheader->gltextures[i][2] = pheader->gltextures[i][1] = pheader->gltextures[i][0]; + pheader->fbtextures[i][3] = pheader->fbtextures[i][2] = pheader->fbtextures[i][1] = pheader->fbtextures[i][0]; + //johnfitz + + pskintype = (daliasskintype_t *)((byte *)(pskintype+1) + size); + } + else + { + // animating skin group. yuck. + pskintype++; + pinskingroup = (daliasskingroup_t *)pskintype; + groupskins = LittleLong (pinskingroup->numskins); + pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); + + pskintype = (daliasskintype_t *)(pinskinintervals + groupskins); + + for (j=0 ; jskinwidth, pheader->skinheight ); + if (j == 0) { + texels = (byte *) Hunk_AllocName(size, loadname); + pheader->texels[i] = texels - (byte *)pheader; + memcpy (texels, (byte *)(pskintype), size); + } + + //johnfitz -- rewritten + q_snprintf (name, sizeof(name), "%s:frame%i_%i", loadmodel->name, i,j); + offset = (src_offset_t)(pskintype) - (src_offset_t)mod_base; //johnfitz + if (Mod_CheckFullbrights ((byte *)(pskintype), size)) + { + pheader->gltextures[i][j&3] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags | TEXPREF_NOBRIGHT); + q_snprintf (fbr_mask_name, sizeof(fbr_mask_name), "%s:frame%i_%i_glow", loadmodel->name, i,j); + pheader->fbtextures[i][j&3] = TexMgr_LoadImage (loadmodel, fbr_mask_name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags | TEXPREF_FULLBRIGHT); + } + else + { + pheader->gltextures[i][j&3] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags); + pheader->fbtextures[i][j&3] = NULL; + } + //johnfitz + + pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + } + k = j; + for (/**/; j < 4; j++) + pheader->gltextures[i][j&3] = pheader->gltextures[i][j - k]; + } + } + + return (void *)pskintype; +} + +//========================================================================= + +/* +================= +Mod_CalcAliasBounds -- johnfitz -- calculate bounds of alias model for nonrotated, yawrotated, and fullrotated cases +================= +*/ +void Mod_CalcAliasBounds (aliashdr_t *a) +{ + int i,j,k; + float dist, yawradius, radius; + vec3_t v; + + //clear out all data + for (i=0; i<3;i++) + { + loadmodel->mins[i] = loadmodel->ymins[i] = loadmodel->rmins[i] = 999999; + loadmodel->maxs[i] = loadmodel->ymaxs[i] = loadmodel->rmaxs[i] = -999999; + radius = yawradius = 0; + } + + //process verts + for (i=0 ; inumposes; i++) + for (j=0; jnumverts; j++) + { + for (k=0; k<3;k++) + v[k] = poseverts[i][j].v[k] * pheader->scale[k] + pheader->scale_origin[k]; + + for (k=0; k<3;k++) + { + loadmodel->mins[k] = q_min(loadmodel->mins[k], v[k]); + loadmodel->maxs[k] = q_max(loadmodel->maxs[k], v[k]); + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + + //rbounds will be used when entity has nonzero pitch or roll + radius = sqrt(radius); + loadmodel->rmins[0] = loadmodel->rmins[1] = loadmodel->rmins[2] = -radius; + loadmodel->rmaxs[0] = loadmodel->rmaxs[1] = loadmodel->rmaxs[2] = radius; + + //ybounds will be used when entity has nonzero yaw + yawradius = sqrt(yawradius); + loadmodel->ymins[0] = loadmodel->ymins[1] = -yawradius; + loadmodel->ymaxs[0] = loadmodel->ymaxs[1] = yawradius; + loadmodel->ymins[2] = loadmodel->mins[2]; + loadmodel->ymaxs[2] = loadmodel->maxs[2]; +} + +static qboolean +nameInList(const char *list, const char *name) +{ + const char *s; + char tmp[MAX_QPATH]; + int i; + + s = list; + + while (*s) + { + // make a copy until the next comma or end of string + i = 0; + while (*s && *s != ',') + { + if (i < MAX_QPATH - 1) + tmp[i++] = *s; + s++; + } + tmp[i] = '\0'; + //compare it to the model name + if (!strcmp(name, tmp)) + { + return true; + } + //search forwards to the next comma or end of string + while (*s && *s == ',') + s++; + } + return false; +} + +/* +================= +Mod_SetExtraFlags -- johnfitz -- set up extra flags that aren't in the mdl +================= +*/ +void Mod_SetExtraFlags (qmodel_t *mod) +{ + extern cvar_t r_nolerp_list, r_noshadow_list; + + if (!mod || mod->type != mod_alias) + return; + + mod->flags &= (0xFF | MF_HOLEY); //only preserve first byte, plus MF_HOLEY + + // nolerp flag + if (nameInList(r_nolerp_list.string, mod->name)) + mod->flags |= MOD_NOLERP; + + // noshadow flag + if (nameInList(r_noshadow_list.string, mod->name)) + mod->flags |= MOD_NOSHADOW; + + // fullbright hack (TODO: make this a cvar list) + if (!strcmp (mod->name, "progs/flame2.mdl") || + !strcmp (mod->name, "progs/flame.mdl") || + !strcmp (mod->name, "progs/boss.mdl")) + mod->flags |= MOD_FBRIGHTHACK; +} + +/* +================= +Mod_LoadAliasModel +================= +*/ +void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) +{ + int i, j; + mdl_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + int version, numframes; + int size; + daliasframetype_t *pframetype; + daliasskintype_t *pskintype; + int start, end, total; + + start = Hunk_LowMark (); + + pinmodel = (mdl_t *)buffer; + mod_base = (byte *)buffer; //johnfitz + + version = LittleLong (pinmodel->version); + if (version != ALIAS_VERSION) + Sys_Error ("%s has wrong version number (%i should be %i)", + mod->name, version, ALIAS_VERSION); + +// +// allocate space for a working header, plus all the data except the frames, +// skin and group info +// + size = sizeof(aliashdr_t) + + (LittleLong (pinmodel->numframes) - 1) * sizeof (pheader->frames[0]); + pheader = (aliashdr_t *) Hunk_AllocName (size, loadname); + + mod->flags = LittleLong (pinmodel->flags); + +// +// endian-adjust and copy the data, starting with the alias model header +// + pheader->boundingradius = LittleFloat (pinmodel->boundingradius); + pheader->numskins = LittleLong (pinmodel->numskins); + pheader->skinwidth = LittleLong (pinmodel->skinwidth); + pheader->skinheight = LittleLong (pinmodel->skinheight); + + if (pheader->skinheight > MAX_LBM_HEIGHT) + Sys_Error ("model %s has a skin taller than %d", mod->name, + MAX_LBM_HEIGHT); + + pheader->numverts = LittleLong (pinmodel->numverts); + + if (pheader->numverts <= 0) + Sys_Error ("model %s has no vertices", mod->name); + + if (pheader->numverts > MAXALIASVERTS) + Sys_Error ("model %s has too many vertices", mod->name); + + pheader->numtris = LittleLong (pinmodel->numtris); + + if (pheader->numtris <= 0) + Sys_Error ("model %s has no triangles", mod->name); + + pheader->numframes = LittleLong (pinmodel->numframes); + numframes = pheader->numframes; + if (numframes < 1) + Sys_Error ("Mod_LoadAliasModel: Invalid # of frames: %d\n", numframes); + + pheader->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO; + mod->synctype = (synctype_t) LittleLong (pinmodel->synctype); + mod->numframes = pheader->numframes; + + for (i=0 ; i<3 ; i++) + { + pheader->scale[i] = LittleFloat (pinmodel->scale[i]); + pheader->scale_origin[i] = LittleFloat (pinmodel->scale_origin[i]); + pheader->eyeposition[i] = LittleFloat (pinmodel->eyeposition[i]); + } + + +// +// load the skins +// + pskintype = (daliasskintype_t *)&pinmodel[1]; + pskintype = (daliasskintype_t *) Mod_LoadAllSkins (pheader->numskins, pskintype); + +// +// load base s and t vertices +// + pinstverts = (stvert_t *)pskintype; + + for (i=0 ; inumverts ; i++) + { + stverts[i].onseam = LittleLong (pinstverts[i].onseam); + stverts[i].s = LittleLong (pinstverts[i].s); + stverts[i].t = LittleLong (pinstverts[i].t); + } + +// +// load triangle lists +// + pintriangles = (dtriangle_t *)&pinstverts[pheader->numverts]; + + for (i=0 ; inumtris ; i++) + { + triangles[i].facesfront = LittleLong (pintriangles[i].facesfront); + + for (j=0 ; j<3 ; j++) + { + triangles[i].vertindex[j] = + LittleLong (pintriangles[i].vertindex[j]); + } + } + +// +// load the frames +// + posenum = 0; + pframetype = (daliasframetype_t *)&pintriangles[pheader->numtris]; + + for (i=0 ; itype); + if (frametype == ALIAS_SINGLE) + pframetype = (daliasframetype_t *) Mod_LoadAliasFrame (pframetype + 1, &pheader->frames[i]); + else + pframetype = (daliasframetype_t *) Mod_LoadAliasGroup (pframetype + 1, &pheader->frames[i]); + } + + pheader->numposes = posenum; + + mod->type = mod_alias; + + Mod_SetExtraFlags (mod); //johnfitz + + Mod_CalcAliasBounds (pheader); //johnfitz + + // + // build the draw lists + // + GL_MakeAliasModelDisplayLists (mod, pheader); + +// +// move the complete, relocatable alias model to the cache +// + end = Hunk_LowMark (); + total = end - start; + + Cache_Alloc (&mod->cache, total, loadname); + if (!mod->cache.data) + return; + memcpy (mod->cache.data, pheader, total); + + Hunk_FreeToLowMark (start); +} + +//============================================================================= + +/* +================= +Mod_LoadSpriteFrame +================= +*/ +void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) +{ + dspriteframe_t *pinframe; + mspriteframe_t *pspriteframe; + int width, height, size, origin[2]; + char name[64]; + src_offset_t offset; //johnfitz + + pinframe = (dspriteframe_t *)pin; + + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + size = width * height; + + pspriteframe = (mspriteframe_t *) Hunk_AllocName (sizeof (mspriteframe_t),loadname); + *ppframe = pspriteframe; + + pspriteframe->width = width; + pspriteframe->height = height; + origin[0] = LittleLong (pinframe->origin[0]); + origin[1] = LittleLong (pinframe->origin[1]); + + pspriteframe->up = origin[1]; + pspriteframe->down = origin[1] - height; + pspriteframe->left = origin[0]; + pspriteframe->right = width + origin[0]; + + //johnfitz -- image might be padded + pspriteframe->smax = (float)width/(float)TexMgr_PadConditional(width); + pspriteframe->tmax = (float)height/(float)TexMgr_PadConditional(height); + //johnfitz + + q_snprintf (name, sizeof(name), "%s:frame%i", loadmodel->name, framenum); + offset = (src_offset_t)(pinframe+1) - (src_offset_t)mod_base; //johnfitz + pspriteframe->gltexture = + TexMgr_LoadImage (loadmodel, name, width, height, SRC_INDEXED, + (byte *)(pinframe + 1), loadmodel->name, offset, + TEXPREF_PAD | TEXPREF_ALPHA | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + + return (void *)((byte *)pinframe + sizeof (dspriteframe_t) + size); +} + + +/* +================= +Mod_LoadSpriteGroup +================= +*/ +void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) +{ + dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + int i, numframes; + dspriteinterval_t *pin_intervals; + float *poutintervals; + void *ptemp; + + pingroup = (dspritegroup_t *)pin; + + numframes = LittleLong (pingroup->numframes); + + pspritegroup = (mspritegroup_t *) Hunk_AllocName (sizeof (mspritegroup_t) + + (numframes - 1) * sizeof (pspritegroup->frames[0]), loadname); + + pspritegroup->numframes = numframes; + + *ppframe = (mspriteframe_t *)pspritegroup; + + pin_intervals = (dspriteinterval_t *)(pingroup + 1); + + poutintervals = (float *) Hunk_AllocName (numframes * sizeof (float), loadname); + + pspritegroup->intervals = poutintervals; + + for (i=0 ; iinterval); + if (*poutintervals <= 0.0) + Sys_Error ("Mod_LoadSpriteGroup: interval<=0"); + + poutintervals++; + pin_intervals++; + } + + ptemp = (void *)pin_intervals; + + for (i=0 ; iframes[i], framenum * 100 + i); + } + + return ptemp; +} + + +/* +================= +Mod_LoadSpriteModel +================= +*/ +void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer) +{ + int i; + int version; + dsprite_t *pin; + msprite_t *psprite; + int numframes; + int size; + dspriteframetype_t *pframetype; + + pin = (dsprite_t *)buffer; + mod_base = (byte *)buffer; //johnfitz + + version = LittleLong (pin->version); + if (version != SPRITE_VERSION) + Sys_Error ("%s has wrong version number " + "(%i should be %i)", mod->name, version, SPRITE_VERSION); + + numframes = LittleLong (pin->numframes); + + size = sizeof (msprite_t) + (numframes - 1) * sizeof (psprite->frames); + + psprite = (msprite_t *) Hunk_AllocName (size, loadname); + + mod->cache.data = psprite; + + psprite->type = LittleLong (pin->type); + psprite->maxwidth = LittleLong (pin->width); + psprite->maxheight = LittleLong (pin->height); + psprite->beamlength = LittleFloat (pin->beamlength); + mod->synctype = (synctype_t) LittleLong (pin->synctype); + psprite->numframes = numframes; + + mod->mins[0] = mod->mins[1] = -psprite->maxwidth/2; + mod->maxs[0] = mod->maxs[1] = psprite->maxwidth/2; + mod->mins[2] = -psprite->maxheight/2; + mod->maxs[2] = psprite->maxheight/2; + +// +// load the frames +// + if (numframes < 1) + Sys_Error ("Mod_LoadSpriteModel: Invalid # of frames: %d\n", numframes); + + mod->numframes = numframes; + + pframetype = (dspriteframetype_t *)(pin + 1); + + for (i=0 ; itype); + psprite->frames[i].type = frametype; + + if (frametype == SPR_SINGLE) + { + pframetype = (dspriteframetype_t *) + Mod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i); + } + else + { + pframetype = (dspriteframetype_t *) + Mod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i); + } + } + + mod->type = mod_sprite; +} + +//============================================================================= + +/* +================ +Mod_Print +================ +*/ +void Mod_Print (void) +{ + int i; + qmodel_t *mod; + + Con_SafePrintf ("Cached models:\n"); //johnfitz -- safeprint instead of print + for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++) + { + Con_SafePrintf ("%8p : %s\n", mod->cache.data, mod->name); //johnfitz -- safeprint instead of print + } + Con_Printf ("%i models\n",mod_numknown); //johnfitz -- print the total too +} + diff --git a/source/gl_model.h b/source/gl_model.h new file mode 100644 index 0000000..6823405 --- /dev/null +++ b/source/gl_model.h @@ -0,0 +1,519 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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__ +#define __MODEL__ + +#include "modelgen.h" +#include "spritegn.h" + +/* + +d*_t structures are on-disk representations +m*_t structures are in-memory + +*/ + +// entity effects + +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 + + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + vec3_t position; +} mvertex_t; + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct mplane_s +{ + vec3_t normal; + float dist; + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +// ericw -- each texture has two chains, so we can clear the model chains +// without affecting the world +typedef enum { + chain_world = 0, + chain_model = 1 +} texchain_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + struct gltexture_s *gltexture; //johnfitz -- pointer to gltexture + struct gltexture_s *fullbright; //johnfitz -- fullbright mask texture + struct gltexture_s *warpimage; //johnfitz -- for water animation + qboolean update_warp; //johnfitz -- update warp this frame + struct msurface_s *texturechains[2]; // for texture chains + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frmae 1 use these + unsigned offsets[MIPLEVELS]; // four mip maps stored +} texture_t; + + +#define SURF_PLANEBACK 2 +#define SURF_DRAWSKY 4 +#define SURF_DRAWSPRITE 8 +#define SURF_DRAWTURB 0x10 +#define SURF_DRAWTILED 0x20 +#define SURF_DRAWBACKGROUND 0x40 +#define SURF_UNDERWATER 0x80 +#define SURF_NOTEXTURE 0x100 //johnfitz +#define SURF_DRAWFENCE 0x200 +#define SURF_DRAWLAVA 0x400 +#define SURF_DRAWSLIME 0x800 +#define SURF_DRAWTELE 0x1000 +#define SURF_DRAWWATER 0x2000 + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + unsigned int v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct +{ + float vecs[2][4]; + float mipadjust; + texture_t *texture; + int flags; +} mtexinfo_t; + +#define VERTEXSIZE 7 + +typedef struct glpoly_s +{ + struct glpoly_s *next; + struct glpoly_s *chain; + int numverts; + float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +} glpoly_t; + +typedef struct msurface_s +{ + int visframe; // should be drawn when node is crossed + qboolean culled; // johnfitz -- for frustum culling + float mins[3]; // johnfitz -- for frustum culling + float maxs[3]; // johnfitz -- for frustum culling + + mplane_t *plane; + int flags; + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + + short texturemins[2]; + short extents[2]; + + int light_s, light_t; // gl lightmap coordinates + + glpoly_t *polys; // multiple if warped + struct msurface_s *texturechain; + + mtexinfo_t *texinfo; + + int vbo_firstvert; // index of this surface's first vert in the VBO + +// lighting info + int dlightframe; + unsigned int dlightbits[(MAX_DLIGHTS + 31) >> 5]; + // int is 32 bits, need an array for MAX_DLIGHTS > 32 + + int lightmaptexturenum; + byte styles[MAXLIGHTMAPS]; + int cached_light[MAXLIGHTMAPS]; // values currently used in lightmap + qboolean cached_dlight; // true if dynamic light in cache + byte *samples; // [numstyles*surfsize] +} msurface_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned int firstsurface; + unsigned int numsurfaces; +} mnode_t; + + + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + efrag_t *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +//johnfitz -- for clipnodes>32k +typedef struct mclipnode_s +{ + int planenum; + int children[2]; // negative numbers are contents +} mclipnode_t; +//johnfitz + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + mclipnode_t *clipnodes; //johnfitz -- was dclipnode_t + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +/* +============================================================================== + +SPRITE MODELS + +============================================================================== +*/ + + +// FIXME: shorten these? +typedef struct mspriteframe_s +{ + int width, height; + float up, down, left, right; + float smax, tmax; //johnfitz -- image might be padded + struct gltexture_s *gltexture; +} mspriteframe_t; + +typedef struct +{ + int numframes; + float *intervals; + mspriteframe_t *frames[1]; +} mspritegroup_t; + +typedef struct +{ + spriteframetype_t type; + mspriteframe_t *frameptr; +} mspriteframedesc_t; + +typedef struct +{ + int type; + int maxwidth; + int maxheight; + int numframes; + float beamlength; // remove? + void *cachespot; // remove? + mspriteframedesc_t frames[1]; +} msprite_t; + + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +//-- from RMQEngine +// split out to keep vertex sizes down +typedef struct aliasmesh_s +{ + float st[2]; + unsigned short vertindex; +} aliasmesh_t; + +typedef struct meshxyz_s +{ + byte xyz[4]; + signed char normal[4]; +} meshxyz_t; + +typedef struct meshst_s +{ + float st[2]; +} meshst_t; +//-- + +typedef struct +{ + int firstpose; + int numposes; + float interval; + trivertx_t bboxmin; + trivertx_t bboxmax; + int frame; + char name[16]; +} maliasframedesc_t; + +typedef struct +{ + trivertx_t bboxmin; + trivertx_t bboxmax; + int frame; +} maliasgroupframedesc_t; + +typedef struct +{ + int numframes; + int intervals; + maliasgroupframedesc_t frames[1]; +} maliasgroup_t; + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct mtriangle_s { + int facesfront; + int vertindex[3]; +} mtriangle_t; + + +#define MAX_SKINS 32 +typedef struct { + 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; + + //ericw -- used to populate vbo + int numverts_vbo; // number of verts with unique x,y,z,s,t + intptr_t meshdesc; // offset into extradata: numverts_vbo aliasmesh_t + int numindexes; + intptr_t indexes; // offset into extradata: numindexes unsigned shorts + intptr_t vertexes; // offset into extradata: numposes*vertsperframe trivertx_t + //ericw -- + + int numposes; + int poseverts; + int posedata; // numposes*poseverts trivert_t + int commands; // gl command list with embedded s/t + struct gltexture_s *gltextures[MAX_SKINS][4]; //johnfitz + struct gltexture_s *fbtextures[MAX_SKINS][4]; //johnfitz + int texels[MAX_SKINS]; // only for player skins + maliasframedesc_t frames[1]; // variable sized +} aliashdr_t; + +#define MAXALIASVERTS 2000 //johnfitz -- was 1024 +#define MAXALIASFRAMES 256 +#define MAXALIASTRIS 2048 +extern aliashdr_t *pheader; +extern stvert_t stverts[MAXALIASVERTS]; +extern mtriangle_t triangles[MAXALIASTRIS]; +extern trivertx_t *poseverts[MAXALIASFRAMES]; + +//=================================================================== + +// +// Whole model +// + +typedef enum {mod_brush, mod_sprite, mod_alias} modtype_t; + +#define EF_ROCKET 1 // leave a trail +#define EF_GRENADE 2 // leave a trail +#define EF_GIB 4 // leave a trail +#define EF_ROTATE 8 // rotate (bonus items) +#define EF_TRACER 16 // green split trail +#define EF_ZOMGIB 32 // small blood trail +#define EF_TRACER2 64 // orange split trail + rotate +#define EF_TRACER3 128 // purple trail +#define MF_HOLEY (1u<<14) // MarkV/QSS -- make index 255 transparent on mdl's + +//johnfitz -- extra flags for rendering +#define MOD_NOLERP 256 //don't lerp when animating +#define MOD_NOSHADOW 512 //don't cast a shadow +#define MOD_FBRIGHTHACK 1024 //when fullbrights are disabled, use a hack to render this model brighter +//johnfitz + +typedef struct qmodel_s +{ + char name[MAX_QPATH]; + unsigned int path_id; // path id of the game directory + // that this model came from + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model graphics +// + vec3_t mins, maxs; + vec3_t ymins, ymaxs; //johnfitz -- bounds for entities with nonzero yaw + vec3_t rmins, rmaxs; //johnfitz -- bounds for entities with nonzero pitch or roll + //johnfitz -- removed float radius; + +// +// solid volume for clipping +// + qboolean clipbox; + vec3_t clipmins, clipmaxs; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + mleaf_t *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + mclipnode_t *clipnodes; //johnfitz -- was dclipnode_t + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + byte *lightdata; + char *entities; + + qboolean viswarn; // for Mod_DecompressVis() + + int bspversion; + +// +// alias model +// + + GLuint meshvbo; + GLuint meshindexesvbo; + int vboindexofs; // offset in vbo of the hdr->numindexes unsigned shorts + int vboxyzofs; // offset in vbo of hdr->numposes*hdr->numverts_vbo meshxyz_t + int vbostofs; // offset in vbo of hdr->numverts_vbo meshst_t + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} qmodel_t; + +//============================================================================ + +void Mod_Init (void); +void Mod_ClearAll (void); +void Mod_ResetAll (void); // for gamedir changes (Host_Game_f) +qmodel_t *Mod_ForName (const char *name, qboolean crash); +void *Mod_Extradata (qmodel_t *mod); // handles caching +void Mod_TouchModel (const char *name); + +mleaf_t *Mod_PointInLeaf (float *p, qmodel_t *model); +byte *Mod_LeafPVS (mleaf_t *leaf, qmodel_t *model); +byte *Mod_NoVisPVS (qmodel_t *model); + +void Mod_SetExtraFlags (qmodel_t *mod); + +#endif // __MODEL__ diff --git a/source/gl_refrag.c b/source/gl_refrag.c new file mode 100644 index 0000000..ee2350c --- /dev/null +++ b/source/gl_refrag.c @@ -0,0 +1,220 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_efrag.c + +#include "quakedef.h" + +mnode_t *r_pefragtopnode; + + +//=========================================================================== + +/* +=============================================================================== + + ENTITY FRAGMENT FUNCTIONS + +ericw -- GLQuake only uses efrags for static entities, and they're never +removed, so I trimmed out unused functionality and fields in efrag_t. + +Now, efrags are just a linked list for each leaf of the static +entities that touch that leaf. The efrags are hunk-allocated so there is no +fixed limit. + +This is inspired by MH's tutorial, and code from RMQEngine. +http://forums.insideqc.com/viewtopic.php?t=1930 + +=============================================================================== +*/ + +vec3_t r_emins, r_emaxs; + +entity_t *r_addent; + + +#define EXTRA_EFRAGS 128 + +// based on RMQEngine +static efrag_t *R_GetEfrag (void) +{ + // we could just Hunk_Alloc a single efrag_t and return it, but since + // the struct is so small (2 pointers) allocate groups of them + // to avoid wasting too much space on the hunk allocation headers. + + if (cl.free_efrags) + { + efrag_t *ef = cl.free_efrags; + cl.free_efrags = ef->leafnext; + ef->leafnext = NULL; + + cl.num_efrags++; + + return ef; + } + else + { + int i; + + cl.free_efrags = (efrag_t *) Hunk_AllocName (EXTRA_EFRAGS * sizeof (efrag_t), "efrags"); + + for (i = 0; i < EXTRA_EFRAGS - 1; i++) + cl.free_efrags[i].leafnext = &cl.free_efrags[i + 1]; + + cl.free_efrags[i].leafnext = NULL; + + // call recursively to get a newly allocated free efrag + return R_GetEfrag (); + } +} + +/* +=================== +R_SplitEntityOnNode +=================== +*/ +void R_SplitEntityOnNode (mnode_t *node) +{ + efrag_t *ef; + mplane_t *splitplane; + mleaf_t *leaf; + int sides; + + if (node->contents == CONTENTS_SOLID) + { + return; + } + +// add an efrag if the node is a leaf + + if ( node->contents < 0) + { + if (!r_pefragtopnode) + r_pefragtopnode = node; + + leaf = (mleaf_t *)node; + +// grab an efrag off the free list + ef = R_GetEfrag(); + ef->entity = r_addent; + +// set the leaf links + ef->leafnext = leaf->efrags; + leaf->efrags = ef; + + return; + } + +// NODE_MIXED + + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE(r_emins, r_emaxs, splitplane); + + if (sides == 3) + { + // split on this plane + // if this is the first splitter of this bmodel, remember it + if (!r_pefragtopnode) + r_pefragtopnode = node; + } + +// recurse down the contacted sides + if (sides & 1) + R_SplitEntityOnNode (node->children[0]); + + if (sides & 2) + R_SplitEntityOnNode (node->children[1]); +} + +/* +=========== +R_CheckEfrags -- johnfitz -- check for excessive efrag count +=========== +*/ +void R_CheckEfrags (void) +{ + if (cls.signon < 2) + return; //don't spam when still parsing signon packet full of static ents + + if (cl.num_efrags > 640 && dev_peakstats.efrags <= 640) + Con_DWarning ("%i efrags exceeds standard limit of 640.\n", cl.num_efrags); + + dev_stats.efrags = cl.num_efrags; + dev_peakstats.efrags = q_max(cl.num_efrags, dev_peakstats.efrags); +} + +/* +=========== +R_AddEfrags +=========== +*/ +void R_AddEfrags (entity_t *ent) +{ + qmodel_t *entmodel; + int i; + + if (!ent->model) + return; + + r_addent = ent; + + r_pefragtopnode = NULL; + + entmodel = ent->model; + + for (i=0 ; i<3 ; i++) + { + r_emins[i] = ent->origin[i] + entmodel->mins[i]; + r_emaxs[i] = ent->origin[i] + entmodel->maxs[i]; + } + + R_SplitEntityOnNode (cl.worldmodel->nodes); + + ent->topnode = r_pefragtopnode; + + R_CheckEfrags (); //johnfitz +} + + +/* +================ +R_StoreEfrags -- johnfitz -- pointless switch statement removed. +================ +*/ +void R_StoreEfrags (efrag_t **ppefrag) +{ + entity_t *pent; + efrag_t *pefrag; + + while ((pefrag = *ppefrag) != NULL) + { + pent = pefrag->entity; + + if ((pent->visframe != r_framecount) && (cl_numvisedicts < MAX_VISEDICTS)) + { + cl_visedicts[cl_numvisedicts++] = pent; + pent->visframe = r_framecount; + } + + ppefrag = &pefrag->leafnext; + } +} + diff --git a/source/gl_rlight.c b/source/gl_rlight.c new file mode 100644 index 0000000..86c73b8 --- /dev/null +++ b/source/gl_rlight.c @@ -0,0 +1,395 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_light.c + +#include "quakedef.h" + +int r_dlightframecount; + +extern cvar_t r_flatlightstyles; //johnfitz + +/* +================== +R_AnimateLight +================== +*/ +void R_AnimateLight (void) +{ + int i,j,k; + +// +// light animations +// 'm' is normal light, 'a' is no light, 'z' is double bright + i = (int)(cl.time*10); + for (j=0 ; jradius * 0.35; + + VectorSubtract (light->origin, r_origin, v); + if (VectorLength (v) < rad) + { // view is inside the dlight + AddLightBlend (1, 0.5, 0, light->radius * 0.0003); + return; + } + + glBegin (GL_TRIANGLE_FAN); + glColor3f (0.2,0.1,0.0); + for (i=0 ; i<3 ; i++) + v[i] = light->origin[i] - vpn[i]*rad; + glVertex3fv (v); + glColor3f (0,0,0); + for (i=16 ; i>=0 ; i--) + { + a = i/16.0 * M_PI*2; + for (j=0 ; j<3 ; j++) + v[j] = light->origin[j] + vright[j]*cos(a)*rad + + vup[j]*sin(a)*rad; + glVertex3fv (v); + } + glEnd (); +} + +/* +============= +R_RenderDlights +============= +*/ +void R_RenderDlights (void) +{ + int i; + dlight_t *l; + + if (!gl_flashblend.value) + return; + + r_dlightframecount = r_framecount + 1; // because the count hasn't + // advanced yet for this frame + glDepthMask (0); + glDisable (GL_TEXTURE_2D); + glShadeModel (GL_SMOOTH); + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + + l = cl_dlights; + for (i=0 ; idie < cl.time || !l->radius) + continue; + R_RenderDlight (l); + } + + glColor3f (1,1,1); + glDisable (GL_BLEND); + glEnable (GL_TEXTURE_2D); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask (1); +} + + +/* +============================================================================= + +DYNAMIC LIGHTS + +============================================================================= +*/ + +/* +============= +R_MarkLights -- johnfitz -- rewritten to use LordHavoc's lighting speedup +============= +*/ +void R_MarkLights (dlight_t *light, int num, mnode_t *node) +{ + mplane_t *splitplane; + msurface_t *surf; + vec3_t impact; + float dist, l, maxdist; + int i, j, s, t; + +start: + + if (node->contents < 0) + return; + + splitplane = node->plane; + if (splitplane->type < 3) + dist = light->origin[splitplane->type] - splitplane->dist; + else + dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist; + + if (dist > light->radius) + { + node = node->children[0]; + goto start; + } + if (dist < -light->radius) + { + node = node->children[1]; + goto start; + } + + maxdist = light->radius*light->radius; +// mark the polygons + surf = cl.worldmodel->surfaces + node->firstsurface; + for (i=0 ; inumsurfaces ; i++, surf++) + { + for (j=0 ; j<3 ; j++) + impact[j] = light->origin[j] - surf->plane->normal[j]*dist; + // clamp center of light to corner and check brightness + l = DotProduct (impact, surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3] - surf->texturemins[0]; + s = l+0.5;if (s < 0) s = 0;else if (s > surf->extents[0]) s = surf->extents[0]; + s = l - s; + l = DotProduct (impact, surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3] - surf->texturemins[1]; + t = l+0.5;if (t < 0) t = 0;else if (t > surf->extents[1]) t = surf->extents[1]; + t = l - t; + // compare to minimum light + if ((s*s+t*t+dist*dist) < maxdist) + { + if (surf->dlightframe != r_dlightframecount) // not dynamic until now + { + surf->dlightbits[num >> 5] = 1U << (num & 31); + surf->dlightframe = r_dlightframecount; + } + else // already dynamic + surf->dlightbits[num >> 5] |= 1U << (num & 31); + } + } + + if (node->children[0]->contents >= 0) + R_MarkLights (light, num, node->children[0]); + if (node->children[1]->contents >= 0) + R_MarkLights (light, num, node->children[1]); +} + +/* +============= +R_PushDlights +============= +*/ +void R_PushDlights (void) +{ + int i; + dlight_t *l; + + if (gl_flashblend.value) + return; + + r_dlightframecount = r_framecount + 1; // because the count hasn't + // advanced yet for this frame + l = cl_dlights; + + for (i=0 ; idie < cl.time || !l->radius) + continue; + R_MarkLights (l, i, cl.worldmodel->nodes); + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +mplane_t *lightplane; +vec3_t lightspot; +vec3_t lightcolor; //johnfitz -- lit support via lordhavoc + +/* +============= +RecursiveLightPoint -- johnfitz -- replaced entire function for lit support via lordhavoc +============= +*/ +int RecursiveLightPoint (vec3_t color, mnode_t *node, vec3_t start, vec3_t end) +{ + float front, back, frac; + vec3_t mid; + +loc0: + if (node->contents < 0) + return false; // didn't hit anything + +// calculate mid point + if (node->plane->type < 3) + { + front = start[node->plane->type] - node->plane->dist; + back = end[node->plane->type] - node->plane->dist; + } + else + { + front = DotProduct(start, node->plane->normal) - node->plane->dist; + back = DotProduct(end, node->plane->normal) - node->plane->dist; + } + + // LordHavoc: optimized recursion + if ((back < 0) == (front < 0)) +// return RecursiveLightPoint (color, node->children[front < 0], start, end); + { + node = node->children[front < 0]; + goto loc0; + } + + frac = front / (front-back); + mid[0] = start[0] + (end[0] - start[0])*frac; + mid[1] = start[1] + (end[1] - start[1])*frac; + mid[2] = start[2] + (end[2] - start[2])*frac; + +// go down front side + if (RecursiveLightPoint (color, node->children[front < 0], start, mid)) + return true; // hit something + else + { + int i, ds, dt; + msurface_t *surf; + // check for impact on this node + VectorCopy (mid, lightspot); + lightplane = node->plane; + + surf = cl.worldmodel->surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surf++) + { + if (surf->flags & SURF_DRAWTILED) + continue; // no lightmaps + + // ericw -- added double casts to force 64-bit precision. + // Without them the zombie at the start of jam3_ericw.bsp was + // incorrectly being lit up in SSE builds. + ds = (int) ((double) DoublePrecisionDotProduct (mid, surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3]); + dt = (int) ((double) DoublePrecisionDotProduct (mid, surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3]); + + if (ds < surf->texturemins[0] || dt < surf->texturemins[1]) + continue; + + ds -= surf->texturemins[0]; + dt -= surf->texturemins[1]; + + if (ds > surf->extents[0] || dt > surf->extents[1]) + continue; + + if (surf->samples) + { + // LordHavoc: enhanced to interpolate lighting + byte *lightmap; + int maps, line3, dsfrac = ds & 15, dtfrac = dt & 15, r00 = 0, g00 = 0, b00 = 0, r01 = 0, g01 = 0, b01 = 0, r10 = 0, g10 = 0, b10 = 0, r11 = 0, g11 = 0, b11 = 0; + float scale; + line3 = ((surf->extents[0]>>4)+1)*3; + + lightmap = surf->samples + ((dt>>4) * ((surf->extents[0]>>4)+1) + (ds>>4))*3; // LordHavoc: *3 for color + + for (maps = 0;maps < MAXLIGHTMAPS && surf->styles[maps] != 255;maps++) + { + scale = (float) d_lightstylevalue[surf->styles[maps]] * 1.0 / 256.0; + r00 += (float) lightmap[ 0] * scale;g00 += (float) lightmap[ 1] * scale;b00 += (float) lightmap[2] * scale; + r01 += (float) lightmap[ 3] * scale;g01 += (float) lightmap[ 4] * scale;b01 += (float) lightmap[5] * scale; + r10 += (float) lightmap[line3+0] * scale;g10 += (float) lightmap[line3+1] * scale;b10 += (float) lightmap[line3+2] * scale; + r11 += (float) lightmap[line3+3] * scale;g11 += (float) lightmap[line3+4] * scale;b11 += (float) lightmap[line3+5] * scale; + lightmap += ((surf->extents[0]>>4)+1) * ((surf->extents[1]>>4)+1)*3; // LordHavoc: *3 for colored lighting + } + + color[0] += (float) ((int) ((((((((r11-r10) * dsfrac) >> 4) + r10)-((((r01-r00) * dsfrac) >> 4) + r00)) * dtfrac) >> 4) + ((((r01-r00) * dsfrac) >> 4) + r00))); + color[1] += (float) ((int) ((((((((g11-g10) * dsfrac) >> 4) + g10)-((((g01-g00) * dsfrac) >> 4) + g00)) * dtfrac) >> 4) + ((((g01-g00) * dsfrac) >> 4) + g00))); + color[2] += (float) ((int) ((((((((b11-b10) * dsfrac) >> 4) + b10)-((((b01-b00) * dsfrac) >> 4) + b00)) * dtfrac) >> 4) + ((((b01-b00) * dsfrac) >> 4) + b00))); + } + return true; // success + } + + // go down back side + return RecursiveLightPoint (color, node->children[front >= 0], mid, end); + } +} + +/* +============= +R_LightPoint -- johnfitz -- replaced entire function for lit support via lordhavoc +============= +*/ +int R_LightPoint (vec3_t p) +{ + vec3_t end; + + if (!cl.worldmodel->lightdata) + { + lightcolor[0] = lightcolor[1] = lightcolor[2] = 255; + return 255; + } + + end[0] = p[0]; + end[1] = p[1]; + end[2] = p[2] - 8192; //johnfitz -- was 2048 + + lightcolor[0] = lightcolor[1] = lightcolor[2] = 0; + RecursiveLightPoint (lightcolor, cl.worldmodel->nodes, p, end); + return ((lightcolor[0] + lightcolor[1] + lightcolor[2]) * (1.0f / 3.0f)); +} diff --git a/source/gl_rmain.c b/source/gl_rmain.c new file mode 100644 index 0000000..ffad937 --- /dev/null +++ b/source/gl_rmain.c @@ -0,0 +1,1148 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +qboolean r_cache_thrash; // compatability + +vec3_t modelorg, r_entorigin; +entity_t *currententity; + +int r_visframecount; // bumped when going to a new PVS +int r_framecount; // used for dlight push checking + +mplane_t frustum[4]; + +//johnfitz -- rendering statistics +int rs_brushpolys, rs_aliaspolys, rs_skypolys, rs_particles, rs_fogpolys; +int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses; +float rs_megatexels; + +// +// view origin +// +vec3_t vup; +vec3_t vpn; +vec3_t vright; +vec3_t r_origin; + +float r_fovx, r_fovy; //johnfitz -- rendering fov may be different becuase of r_waterwarp and r_stereo + +// +// screen size info +// +refdef_t r_refdef; + +mleaf_t *r_viewleaf, *r_oldviewleaf; + +int d_lightstylevalue[256]; // 8.8 fraction of base light value + + +cvar_t r_norefresh = {"r_norefresh","0",CVAR_NONE}; +cvar_t r_drawentities = {"r_drawentities","1",CVAR_NONE}; +cvar_t r_drawviewmodel = {"r_drawviewmodel","1",CVAR_NONE}; +cvar_t r_speeds = {"r_speeds","0",CVAR_NONE}; +cvar_t r_pos = {"r_pos","0",CVAR_NONE}; +cvar_t r_fullbright = {"r_fullbright","0",CVAR_NONE}; +cvar_t r_lightmap = {"r_lightmap","0",CVAR_NONE}; +cvar_t r_shadows = {"r_shadows","0",CVAR_ARCHIVE}; +cvar_t r_wateralpha = {"r_wateralpha","1",CVAR_ARCHIVE}; +cvar_t r_dynamic = {"r_dynamic","1",CVAR_ARCHIVE}; +cvar_t r_novis = {"r_novis","0",CVAR_ARCHIVE}; + +cvar_t gl_finish = {"gl_finish","0",CVAR_NONE}; +cvar_t gl_clear = {"gl_clear","1",CVAR_NONE}; +cvar_t gl_cull = {"gl_cull","1",CVAR_NONE}; +cvar_t gl_smoothmodels = {"gl_smoothmodels","1",CVAR_NONE}; +cvar_t gl_affinemodels = {"gl_affinemodels","0",CVAR_NONE}; +cvar_t gl_polyblend = {"gl_polyblend","1",CVAR_NONE}; +cvar_t gl_flashblend = {"gl_flashblend","0",CVAR_ARCHIVE}; +cvar_t gl_playermip = {"gl_playermip","0",CVAR_NONE}; +cvar_t gl_nocolors = {"gl_nocolors","0",CVAR_NONE}; + +//johnfitz -- new cvars +cvar_t r_stereo = {"r_stereo","0",CVAR_NONE}; +cvar_t r_stereodepth = {"r_stereodepth","128",CVAR_NONE}; +cvar_t r_clearcolor = {"r_clearcolor","2",CVAR_ARCHIVE}; +cvar_t r_drawflat = {"r_drawflat","0",CVAR_NONE}; +cvar_t r_flatlightstyles = {"r_flatlightstyles", "0", CVAR_NONE}; +cvar_t gl_fullbrights = {"gl_fullbrights", "1", CVAR_ARCHIVE}; +cvar_t gl_farclip = {"gl_farclip", "16384", CVAR_ARCHIVE}; +cvar_t gl_overbright = {"gl_overbright", "1", CVAR_ARCHIVE}; +cvar_t gl_overbright_models = {"gl_overbright_models", "1", CVAR_ARCHIVE}; +cvar_t r_oldskyleaf = {"r_oldskyleaf", "0", CVAR_NONE}; +cvar_t r_drawworld = {"r_drawworld", "1", CVAR_NONE}; +cvar_t r_showtris = {"r_showtris", "0", CVAR_NONE}; +cvar_t r_showbboxes = {"r_showbboxes", "0", CVAR_NONE}; +cvar_t r_lerpmodels = {"r_lerpmodels", "1", CVAR_NONE}; +cvar_t r_lerpmove = {"r_lerpmove", "1", CVAR_NONE}; +cvar_t r_nolerp_list = {"r_nolerp_list", "progs/flame.mdl,progs/flame2.mdl,progs/braztall.mdl,progs/brazshrt.mdl,progs/longtrch.mdl,progs/flame_pyre.mdl,progs/v_saw.mdl,progs/v_xfist.mdl,progs/h2stuff/newfire.mdl", CVAR_NONE}; +cvar_t r_noshadow_list = {"r_noshadow_list", "progs/flame2.mdl,progs/flame.mdl,progs/bolt1.mdl,progs/bolt2.mdl,progs/bolt3.mdl,progs/laser.mdl", CVAR_NONE}; + +extern cvar_t r_vfog; +//johnfitz + +cvar_t gl_zfix = {"gl_zfix", "0", CVAR_NONE}; // QuakeSpasm z-fighting fix + +cvar_t r_lavaalpha = {"r_lavaalpha","0",CVAR_NONE}; +cvar_t r_telealpha = {"r_telealpha","0",CVAR_NONE}; +cvar_t r_slimealpha = {"r_slimealpha","0",CVAR_NONE}; + +float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; + +qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz + +cvar_t r_scale = {"r_scale", "1", CVAR_ARCHIVE}; + +//============================================================================== +// +// GLSL GAMMA CORRECTION +// +//============================================================================== + +static GLuint r_gamma_texture; +static GLuint r_gamma_program; +static int r_gamma_texture_width, r_gamma_texture_height; + +// uniforms used in gamma shader +static GLuint gammaLoc; +static GLuint contrastLoc; +static GLuint textureLoc; + +/* +============= +GLSLGamma_DeleteTexture +============= +*/ +void GLSLGamma_DeleteTexture (void) +{ + glDeleteTextures (1, &r_gamma_texture); + r_gamma_texture = 0; + r_gamma_program = 0; // deleted in R_DeleteShaders +} + +/* +============= +GLSLGamma_CreateShaders +============= +*/ +static void GLSLGamma_CreateShaders (void) +{ + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "void main(void) {\n" + " gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n" + " gl_TexCoord[0] = gl_MultiTexCoord0;\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D GammaTexture;\n" + "uniform float GammaValue;\n" + "uniform float ContrastValue;\n" + "\n" + "void main(void) {\n" + " vec4 frag = texture2D(GammaTexture, gl_TexCoord[0].xy);\n" + " frag.rgb = frag.rgb * ContrastValue;\n" + " gl_FragColor = vec4(pow(frag.rgb, vec3(GammaValue)), 1.0);\n" + "}\n"; + + if (!gl_glsl_gamma_able) + return; + + r_gamma_program = GL_CreateProgram (vertSource, fragSource, 0, NULL); + +// get uniform locations + gammaLoc = GL_GetUniformLocation (&r_gamma_program, "GammaValue"); + contrastLoc = GL_GetUniformLocation (&r_gamma_program, "ContrastValue"); + textureLoc = GL_GetUniformLocation (&r_gamma_program, "GammaTexture"); +} + +/* +============= +GLSLGamma_GammaCorrect +============= +*/ +void GLSLGamma_GammaCorrect (void) +{ + float smax, tmax; + + if (!gl_glsl_gamma_able) + return; + + if (vid_gamma.value == 1 && vid_contrast.value == 1) + return; + +// create render-to-texture texture if needed + if (!r_gamma_texture) + { + glGenTextures (1, &r_gamma_texture); + glBindTexture (GL_TEXTURE_2D, r_gamma_texture); + + r_gamma_texture_width = glwidth; + r_gamma_texture_height = glheight; + + if (!gl_texture_NPOT) + { + r_gamma_texture_width = TexMgr_Pad(r_gamma_texture_width); + r_gamma_texture_height = TexMgr_Pad(r_gamma_texture_height); + } + + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, r_gamma_texture_width, r_gamma_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + +// create shader if needed + if (!r_gamma_program) + { + GLSLGamma_CreateShaders (); + if (!r_gamma_program) + { + Sys_Error("GLSLGamma_CreateShaders failed"); + } + } + +// copy the framebuffer to the texture + GL_DisableMultitexture(); + glBindTexture (GL_TEXTURE_2D, r_gamma_texture); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, glx, gly, glwidth, glheight); + +// draw the texture back to the framebuffer with a fragment shader + GL_UseProgramFunc (r_gamma_program); + GL_Uniform1fFunc (gammaLoc, vid_gamma.value); + GL_Uniform1fFunc (contrastLoc, q_min(2.0, q_max(1.0, vid_contrast.value))); + GL_Uniform1iFunc (textureLoc, 0); // use texture unit 0 + + glDisable (GL_ALPHA_TEST); + glDisable (GL_DEPTH_TEST); + + glViewport (glx, gly, glwidth, glheight); + + smax = glwidth/(float)r_gamma_texture_width; + tmax = glheight/(float)r_gamma_texture_height; + + glBegin (GL_QUADS); + glTexCoord2f (0, 0); + glVertex2f (-1, -1); + glTexCoord2f (smax, 0); + glVertex2f (1, -1); + glTexCoord2f (smax, tmax); + glVertex2f (1, 1); + glTexCoord2f (0, tmax); + glVertex2f (-1, 1); + glEnd (); + + GL_UseProgramFunc (0); + +// clear cached binding + GL_ClearBindings (); +} + +/* +================= +R_CullBox -- johnfitz -- replaced with new function from lordhavoc + +Returns true if the box is completely outside the frustum +================= +*/ +qboolean R_CullBox (vec3_t emins, vec3_t emaxs) +{ + int i; + mplane_t *p; + for (i = 0;i < 4;i++) + { + p = frustum + i; + switch(p->signbits) + { + default: + case 0: + if (p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2] < p->dist) + return true; + break; + case 1: + if (p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2] < p->dist) + return true; + break; + case 2: + if (p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2] < p->dist) + return true; + break; + case 3: + if (p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2] < p->dist) + return true; + break; + case 4: + if (p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2] < p->dist) + return true; + break; + case 5: + if (p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2] < p->dist) + return true; + break; + case 6: + if (p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2] < p->dist) + return true; + break; + case 7: + if (p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2] < p->dist) + return true; + break; + } + } + return false; +} +/* +=============== +R_CullModelForEntity -- johnfitz -- uses correct bounds based on rotation +=============== +*/ +qboolean R_CullModelForEntity (entity_t *e) +{ + vec3_t mins, maxs; + + if (e->angles[0] || e->angles[2]) //pitch or roll + { + VectorAdd (e->origin, e->model->rmins, mins); + VectorAdd (e->origin, e->model->rmaxs, maxs); + } + else if (e->angles[1]) //yaw + { + VectorAdd (e->origin, e->model->ymins, mins); + VectorAdd (e->origin, e->model->ymaxs, maxs); + } + else //no rotation + { + VectorAdd (e->origin, e->model->mins, mins); + VectorAdd (e->origin, e->model->maxs, maxs); + } + + return R_CullBox (mins, maxs); +} + +/* +=============== +R_RotateForEntity -- johnfitz -- modified to take origin and angles instead of pointer to entity +=============== +*/ +void R_RotateForEntity (vec3_t origin, vec3_t angles) +{ + glTranslatef (origin[0], origin[1], origin[2]); + glRotatef (angles[1], 0, 0, 1); + glRotatef (-angles[0], 0, 1, 0); + glRotatef (angles[2], 1, 0, 0); +} + +/* +============= +GL_PolygonOffset -- johnfitz + +negative offset moves polygon closer to camera +============= +*/ +void GL_PolygonOffset (int offset) +{ + if (offset > 0) + { + glEnable (GL_POLYGON_OFFSET_FILL); + glEnable (GL_POLYGON_OFFSET_LINE); + glPolygonOffset(1, offset); + } + else if (offset < 0) + { + glEnable (GL_POLYGON_OFFSET_FILL); + glEnable (GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-1, offset); + } + else + { + glDisable (GL_POLYGON_OFFSET_FILL); + glDisable (GL_POLYGON_OFFSET_LINE); + } +} + +//============================================================================== +// +// SETUP FRAME +// +//============================================================================== + +int SignbitsForPlane (mplane_t *out) +{ + int bits, j; + + // for fast box on planeside test + + bits = 0; + for (j=0 ; j<3 ; j++) + { + if (out->normal[j] < 0) + bits |= 1<contents); + V_CalcBlend (); + + r_cache_thrash = false; + + //johnfitz -- calculate r_fovx and r_fovy here + r_fovx = r_refdef.fov_x; + r_fovy = r_refdef.fov_y; + if (r_waterwarp.value) + { + int contents = Mod_PointInLeaf (r_origin, cl.worldmodel)->contents; + if (contents == CONTENTS_WATER || contents == CONTENTS_SLIME || contents == CONTENTS_LAVA) + { + //variance is a percentage of width, where width = 2 * tan(fov / 2) otherwise the effect is too dramatic at high FOV and too subtle at low FOV. what a mess! + r_fovx = atan(tan(DEG2RAD(r_refdef.fov_x) / 2) * (0.97 + sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; + r_fovy = atan(tan(DEG2RAD(r_refdef.fov_y) / 2) * (1.03 - sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; + } + } + //johnfitz + + R_SetFrustum (r_fovx, r_fovy); //johnfitz -- use r_fov* vars + + R_MarkSurfaces (); //johnfitz -- create texture chains from PVS + + R_CullSurfaces (); //johnfitz -- do after R_SetFrustum and R_MarkSurfaces + + R_UpdateWarpTextures (); //johnfitz -- do this before R_Clear + + R_Clear (); + + //johnfitz -- cheat-protect some draw modes + r_drawflat_cheatsafe = r_fullbright_cheatsafe = r_lightmap_cheatsafe = false; + r_drawworld_cheatsafe = true; + if (cl.maxclients == 1) + { + if (!r_drawworld.value) r_drawworld_cheatsafe = false; + + if (r_drawflat.value) r_drawflat_cheatsafe = true; + else if (r_fullbright.value || !cl.worldmodel->lightdata) r_fullbright_cheatsafe = true; + else if (r_lightmap.value) r_lightmap_cheatsafe = true; + } + //johnfitz +} + +//============================================================================== +// +// RENDER VIEW +// +//============================================================================== + +/* +============= +R_DrawEntitiesOnList +============= +*/ +void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter +{ + int i; + + if (!r_drawentities.value) + return; + + //johnfitz -- sprites are not a special case + for (i=0 ; ialpha) < 1 && !alphapass) || + (ENTALPHA_DECODE(currententity->alpha) == 1 && alphapass)) + continue; + + //johnfitz -- chasecam + if (currententity == &cl_entities[cl.viewentity]) + currententity->angles[0] *= 0.3; + //johnfitz + + switch (currententity->model->type) + { + case mod_alias: + R_DrawAliasModel (currententity); + break; + case mod_brush: + R_DrawBrushModel (currententity); + break; + case mod_sprite: + R_DrawSpriteModel (currententity); + break; + } + } +} + +/* +============= +R_DrawViewModel -- johnfitz -- gutted +============= +*/ +void R_DrawViewModel (void) +{ + if (!r_drawviewmodel.value || !r_drawentities.value || chase_active.value) + return; + + if (cl.items & IT_INVISIBILITY || cl.stats[STAT_HEALTH] <= 0) + return; + + currententity = &cl.viewent; + if (!currententity->model) + return; + + //johnfitz -- this fixes a crash + if (currententity->model->type != mod_alias) + return; + //johnfitz + + // hack the depth range to prevent view model from poking into walls + glDepthRange (0, 0.3); + R_DrawAliasModel (currententity); + glDepthRange (0, 1); +} + +/* +================ +R_EmitWirePoint -- johnfitz -- draws a wireframe cross shape for point entities +================ +*/ +void R_EmitWirePoint (vec3_t origin) +{ + int size=8; + + glBegin (GL_LINES); + glVertex3f (origin[0]-size, origin[1], origin[2]); + glVertex3f (origin[0]+size, origin[1], origin[2]); + glVertex3f (origin[0], origin[1]-size, origin[2]); + glVertex3f (origin[0], origin[1]+size, origin[2]); + glVertex3f (origin[0], origin[1], origin[2]-size); + glVertex3f (origin[0], origin[1], origin[2]+size); + glEnd (); +} + +/* +================ +R_EmitWireBox -- johnfitz -- draws one axis aligned bounding box +================ +*/ +void R_EmitWireBox (vec3_t mins, vec3_t maxs) +{ + glBegin (GL_QUAD_STRIP); + glVertex3f (mins[0], mins[1], mins[2]); + glVertex3f (mins[0], mins[1], maxs[2]); + glVertex3f (maxs[0], mins[1], mins[2]); + glVertex3f (maxs[0], mins[1], maxs[2]); + glVertex3f (maxs[0], maxs[1], mins[2]); + glVertex3f (maxs[0], maxs[1], maxs[2]); + glVertex3f (mins[0], maxs[1], mins[2]); + glVertex3f (mins[0], maxs[1], maxs[2]); + glVertex3f (mins[0], mins[1], mins[2]); + glVertex3f (mins[0], mins[1], maxs[2]); + glEnd (); +} + +/* +================ +R_ShowBoundingBoxes -- johnfitz + +draw bounding boxes -- the server-side boxes, not the renderer cullboxes +================ +*/ +void R_ShowBoundingBoxes (void) +{ + extern edict_t *sv_player; + vec3_t mins,maxs; + edict_t *ed; + int i; + + if (!r_showbboxes.value || cl.maxclients > 1 || !r_drawentities.value || !sv.active) + return; + + glDisable (GL_DEPTH_TEST); + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + GL_PolygonOffset (OFFSET_SHOWTRIS); + glDisable (GL_TEXTURE_2D); + glDisable (GL_CULL_FACE); + glColor3f (1,1,1); + + for (i=0, ed=NEXT_EDICT(sv.edicts) ; iv.mins[0] == ed->v.maxs[0] && ed->v.mins[1] == ed->v.maxs[1] && ed->v.mins[2] == ed->v.maxs[2]) + { + //point entity + R_EmitWirePoint (ed->v.origin); + } + else + { + //box entity + VectorAdd (ed->v.mins, ed->v.origin, mins); + VectorAdd (ed->v.maxs, ed->v.origin, maxs); + R_EmitWireBox (mins, maxs); + } + } + + glColor3f (1,1,1); + glEnable (GL_TEXTURE_2D); + glEnable (GL_CULL_FACE); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + GL_PolygonOffset (OFFSET_NONE); + glEnable (GL_DEPTH_TEST); + + Sbar_Changed (); //so we don't get dots collecting on the statusbar +} + +/* +================ +R_ShowTris -- johnfitz +================ +*/ +void R_ShowTris (void) +{ + extern cvar_t r_particles; + int i; + + if (r_showtris.value < 1 || r_showtris.value > 2 || cl.maxclients > 1) + return; + + if (r_showtris.value == 1) + glDisable (GL_DEPTH_TEST); + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + GL_PolygonOffset (OFFSET_SHOWTRIS); + glDisable (GL_TEXTURE_2D); + glColor3f (1,1,1); +// glEnable (GL_BLEND); +// glBlendFunc (GL_ONE, GL_ONE); + + if (r_drawworld.value) + { + R_DrawWorld_ShowTris (); + } + + if (r_drawentities.value) + { + for (i=0 ; iangles[0] *= 0.3; + + switch (currententity->model->type) + { + case mod_brush: + R_DrawBrushModel_ShowTris (currententity); + break; + case mod_alias: + R_DrawAliasModel_ShowTris (currententity); + break; + case mod_sprite: + R_DrawSpriteModel (currententity); + break; + default: + break; + } + } + + // viewmodel + currententity = &cl.viewent; + if (r_drawviewmodel.value + && !chase_active.value + && cl.stats[STAT_HEALTH] > 0 + && !(cl.items & IT_INVISIBILITY) + && currententity->model + && currententity->model->type == mod_alias) + { + glDepthRange (0, 0.3); + R_DrawAliasModel_ShowTris (currententity); + glDepthRange (0, 1); + } + } + + if (r_particles.value) + { + R_DrawParticles_ShowTris (); + } + +// glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +// glDisable (GL_BLEND); + glColor3f (1,1,1); + glEnable (GL_TEXTURE_2D); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + GL_PolygonOffset (OFFSET_NONE); + if (r_showtris.value == 1) + glEnable (GL_DEPTH_TEST); + + Sbar_Changed (); //so we don't get dots collecting on the statusbar +} + +/* +================ +R_DrawShadows +================ +*/ +void R_DrawShadows (void) +{ + int i; + + if (!r_shadows.value || !r_drawentities.value || r_drawflat_cheatsafe || r_lightmap_cheatsafe) + return; + + // Use stencil buffer to prevent self-intersecting shadows, from Baker (MarkV) + if (gl_stencilbits) + { + glClear(GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_EQUAL, 0, ~0); + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + glEnable(GL_STENCIL_TEST); + } + + for (i=0 ; imodel->type != mod_alias) + continue; + + if (currententity == &cl.viewent) + return; + + GL_DrawAliasShadow (currententity); + } + + if (gl_stencilbits) + { + glDisable(GL_STENCIL_TEST); + } +} + +/* +================ +R_RenderScene +================ +*/ +void R_RenderScene (void) +{ + R_SetupScene (); //johnfitz -- this does everything that should be done once per call to RenderScene + + Fog_EnableGFog (); //johnfitz + + Sky_DrawSky (); //johnfitz + + R_DrawWorld (); + + S_ExtraUpdate (); // don't let sound get messed up if going slow + + R_DrawShadows (); //johnfitz -- render entity shadows + + R_DrawEntitiesOnList (false); //johnfitz -- false means this is the pass for nonalpha entities + + R_DrawWorld_Water (); //johnfitz -- drawn here since they might have transparency + + R_DrawEntitiesOnList (true); //johnfitz -- true means this is the pass for alpha entities + + R_RenderDlights (); //triangle fan dlights -- johnfitz -- moved after water + + R_DrawParticles (); + + Fog_DisableGFog (); //johnfitz + + R_DrawViewModel (); //johnfitz -- moved here from R_RenderView + + R_ShowTris (); //johnfitz + + R_ShowBoundingBoxes (); //johnfitz +} + +static GLuint r_scaleview_texture; +static int r_scaleview_texture_width, r_scaleview_texture_height; + +/* +============= +R_ScaleView_DeleteTexture +============= +*/ +void R_ScaleView_DeleteTexture (void) +{ + glDeleteTextures (1, &r_scaleview_texture); + r_scaleview_texture = 0; +} + +/* +================ +R_ScaleView + +The r_scale cvar allows rendering the 3D view at 1/2, 1/3, or 1/4 resolution. +This function scales the reduced resolution 3D view back up to fill +r_refdef.vrect. This is for emulating a low-resolution pixellated look, +or possibly as a perforance boost on slow graphics cards. +================ +*/ +void R_ScaleView (void) +{ + float smax, tmax; + int scale; + int srcx, srcy, srcw, srch; + + // copied from R_SetupGL() + scale = CLAMP(1, (int)r_scale.value, 4); + srcx = glx + r_refdef.vrect.x; + srcy = gly + glheight - r_refdef.vrect.y - r_refdef.vrect.height; + srcw = r_refdef.vrect.width / scale; + srch = r_refdef.vrect.height / scale; + + if (scale == 1) + return; + + // make sure texture unit 0 is selected + GL_DisableMultitexture (); + + // create (if needed) and bind the render-to-texture texture + if (!r_scaleview_texture) + { + glGenTextures (1, &r_scaleview_texture); + + r_scaleview_texture_width = 0; + r_scaleview_texture_height = 0; + } + glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); + + // resize render-to-texture texture if needed + if (r_scaleview_texture_width < srcw + || r_scaleview_texture_height < srch) + { + r_scaleview_texture_width = srcw; + r_scaleview_texture_height = srch; + + if (!gl_texture_NPOT) + { + r_scaleview_texture_width = TexMgr_Pad(r_scaleview_texture_width); + r_scaleview_texture_height = TexMgr_Pad(r_scaleview_texture_height); + } + + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, r_scaleview_texture_width, r_scaleview_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + // copy the framebuffer to the texture + glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch); + + // draw the texture back to the framebuffer + glDisable (GL_ALPHA_TEST); + glDisable (GL_DEPTH_TEST); + glDisable (GL_CULL_FACE); + glDisable (GL_BLEND); + + glViewport (srcx, srcy, r_refdef.vrect.width, r_refdef.vrect.height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity (); + + // correction factor if we lack NPOT textures, normally these are 1.0f + smax = srcw/(float)r_scaleview_texture_width; + tmax = srch/(float)r_scaleview_texture_height; + + glBegin (GL_QUADS); + glTexCoord2f (0, 0); + glVertex2f (-1, -1); + glTexCoord2f (smax, 0); + glVertex2f (1, -1); + glTexCoord2f (smax, tmax); + glVertex2f (1, 1); + glTexCoord2f (0, tmax); + glVertex2f (-1, 1); + glEnd (); + + // clear cached binding + GL_ClearBindings (); +} + +/* +================ +R_RenderView +================ +*/ +void R_RenderView (void) +{ + double time1, time2; + + if (r_norefresh.value) + return; + + if (!cl.worldmodel) + Sys_Error ("R_RenderView: NULL worldmodel"); + + time1 = 0; /* avoid compiler warning */ + if (r_speeds.value) + { + glFinish (); + time1 = Sys_DoubleTime (); + + //johnfitz -- rendering statistics + rs_brushpolys = rs_aliaspolys = rs_skypolys = rs_particles = rs_fogpolys = rs_megatexels = + rs_dynamiclightmaps = rs_aliaspasses = rs_skypasses = rs_brushpasses = 0; + } + else if (gl_finish.value) + glFinish (); + + R_SetupView (); //johnfitz -- this does everything that should be done once per frame + + //johnfitz -- stereo rendering -- full of hacky goodness + if (r_stereo.value) + { + float eyesep = CLAMP(-8.0f, r_stereo.value, 8.0f); + float fdepth = CLAMP(32.0f, r_stereodepth.value, 1024.0f); + + AngleVectors (r_refdef.viewangles, vpn, vright, vup); + + //render left eye (red) + glColorMask(1, 0, 0, 1); + VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); + frustum_skew = 0.5 * eyesep * NEARCLIP / fdepth; + srand((int) (cl.time * 1000)); //sync random stuff between eyes + + R_RenderScene (); + + //render right eye (cyan) + glClear (GL_DEPTH_BUFFER_BIT); + glColorMask(0, 1, 1, 1); + VectorMA (r_refdef.vieworg, 1.0f * eyesep, vright, r_refdef.vieworg); + frustum_skew = -frustum_skew; + srand((int) (cl.time * 1000)); //sync random stuff between eyes + + R_RenderScene (); + + //restore + glColorMask(1, 1, 1, 1); + VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); + frustum_skew = 0.0f; + } + else + { + R_RenderScene (); + } + //johnfitz + + R_ScaleView (); + + //johnfitz -- modified r_speeds output + time2 = Sys_DoubleTime (); + if (r_pos.value) + Con_Printf ("x %i y %i z %i (pitch %i yaw %i roll %i)\n", + (int)cl_entities[cl.viewentity].origin[0], + (int)cl_entities[cl.viewentity].origin[1], + (int)cl_entities[cl.viewentity].origin[2], + (int)cl.viewangles[PITCH], + (int)cl.viewangles[YAW], + (int)cl.viewangles[ROLL]); + else if (r_speeds.value == 2) + Con_Printf ("%3i ms %4i/%4i wpoly %4i/%4i epoly %3i lmap %4i/%4i sky %1.1f mtex\n", + (int)((time2-time1)*1000), + rs_brushpolys, + rs_brushpasses, + rs_aliaspolys, + rs_aliaspasses, + rs_dynamiclightmaps, + rs_skypolys, + rs_skypasses, + TexMgr_FrameUsage ()); + else if (r_speeds.value) + Con_Printf ("%3i ms %4i wpoly %4i epoly %3i lmap\n", + (int)((time2-time1)*1000), + rs_brushpolys, + rs_aliaspolys, + rs_dynamiclightmaps); + //johnfitz +} + diff --git a/source/gl_rmisc.c b/source/gl_rmisc.c new file mode 100644 index 0000000..4e742a8 --- /dev/null +++ b/source/gl_rmisc.c @@ -0,0 +1,651 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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_misc.c + +#include "quakedef.h" + +//johnfitz -- new cvars +extern cvar_t r_stereo; +extern cvar_t r_stereodepth; +extern cvar_t r_clearcolor; +extern cvar_t r_drawflat; +extern cvar_t r_flatlightstyles; +extern cvar_t gl_fullbrights; +extern cvar_t gl_farclip; +extern cvar_t gl_overbright; +extern cvar_t gl_overbright_models; +extern cvar_t r_waterquality; +extern cvar_t r_oldwater; +extern cvar_t r_waterwarp; +extern cvar_t r_oldskyleaf; +extern cvar_t r_drawworld; +extern cvar_t r_showtris; +extern cvar_t r_showbboxes; +extern cvar_t r_lerpmodels; +extern cvar_t r_lerpmove; +extern cvar_t r_nolerp_list; +extern cvar_t r_noshadow_list; +//johnfitz +extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix + +extern gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz + + +/* +==================== +GL_Overbright_f -- johnfitz +==================== +*/ +static void GL_Overbright_f (cvar_t *var) +{ + R_RebuildAllLightmaps (); +} + +/* +==================== +GL_Fullbrights_f -- johnfitz +==================== +*/ +static void GL_Fullbrights_f (cvar_t *var) +{ + TexMgr_ReloadNobrightImages (); +} + +/* +==================== +R_SetClearColor_f -- johnfitz +==================== +*/ +static void R_SetClearColor_f (cvar_t *var) +{ + byte *rgb; + int s; + + s = (int)r_clearcolor.value & 0xFF; + rgb = (byte*)(d_8to24table + s); + glClearColor (rgb[0]/255.0,rgb[1]/255.0,rgb[2]/255.0,0); +} + +/* +==================== +R_Novis_f -- johnfitz +==================== +*/ +static void R_VisChanged (cvar_t *var) +{ + extern int vis_changed; + vis_changed = 1; +} + +/* +=============== +R_Model_ExtraFlags_List_f -- johnfitz -- called when r_nolerp_list or r_noshadow_list cvar changes +=============== +*/ +static void R_Model_ExtraFlags_List_f (cvar_t *var) +{ + int i; + for (i=0; i < MAX_MODELS; i++) + Mod_SetExtraFlags (cl.model_precache[i]); +} + +/* +==================== +R_SetWateralpha_f -- ericw +==================== +*/ +static void R_SetWateralpha_f (cvar_t *var) +{ + map_wateralpha = var->value; +} + +/* +==================== +R_SetLavaalpha_f -- ericw +==================== +*/ +static void R_SetLavaalpha_f (cvar_t *var) +{ + map_lavaalpha = var->value; +} + +/* +==================== +R_SetTelealpha_f -- ericw +==================== +*/ +static void R_SetTelealpha_f (cvar_t *var) +{ + map_telealpha = var->value; +} + +/* +==================== +R_SetSlimealpha_f -- ericw +==================== +*/ +static void R_SetSlimealpha_f (cvar_t *var) +{ + map_slimealpha = var->value; +} + +/* +==================== +GL_WaterAlphaForSurfface -- ericw +==================== +*/ +float GL_WaterAlphaForSurface (msurface_t *fa) +{ + if (fa->flags & SURF_DRAWLAVA) + return map_lavaalpha > 0 ? map_lavaalpha : map_wateralpha; + else if (fa->flags & SURF_DRAWTELE) + return map_telealpha > 0 ? map_telealpha : map_wateralpha; + else if (fa->flags & SURF_DRAWSLIME) + return map_slimealpha > 0 ? map_slimealpha : map_wateralpha; + else + return map_wateralpha; +} + + +/* +=============== +R_Init +=============== +*/ +void R_Init (void) +{ + extern cvar_t gl_finish; + + Cmd_AddCommand ("timerefresh", R_TimeRefresh_f); + Cmd_AddCommand ("pointfile", R_ReadPointFile_f); + + Cvar_RegisterVariable (&r_norefresh); + Cvar_RegisterVariable (&r_lightmap); + Cvar_RegisterVariable (&r_fullbright); + Cvar_RegisterVariable (&r_drawentities); + Cvar_RegisterVariable (&r_drawviewmodel); + Cvar_RegisterVariable (&r_shadows); + Cvar_RegisterVariable (&r_wateralpha); + Cvar_SetCallback (&r_wateralpha, R_SetWateralpha_f); + Cvar_RegisterVariable (&r_dynamic); + Cvar_RegisterVariable (&r_novis); + Cvar_SetCallback (&r_novis, R_VisChanged); + Cvar_RegisterVariable (&r_speeds); + Cvar_RegisterVariable (&r_pos); + + Cvar_RegisterVariable (&gl_finish); + Cvar_RegisterVariable (&gl_clear); + Cvar_RegisterVariable (&gl_cull); + Cvar_RegisterVariable (&gl_smoothmodels); + Cvar_RegisterVariable (&gl_affinemodels); + Cvar_RegisterVariable (&gl_polyblend); + Cvar_RegisterVariable (&gl_flashblend); + Cvar_RegisterVariable (&gl_playermip); + Cvar_RegisterVariable (&gl_nocolors); + + //johnfitz -- new cvars + Cvar_RegisterVariable (&r_stereo); + Cvar_RegisterVariable (&r_stereodepth); + Cvar_RegisterVariable (&r_clearcolor); + Cvar_SetCallback (&r_clearcolor, R_SetClearColor_f); + Cvar_RegisterVariable (&r_waterquality); + Cvar_RegisterVariable (&r_oldwater); + Cvar_RegisterVariable (&r_waterwarp); + Cvar_RegisterVariable (&r_drawflat); + Cvar_RegisterVariable (&r_flatlightstyles); + Cvar_RegisterVariable (&r_oldskyleaf); + Cvar_SetCallback (&r_oldskyleaf, R_VisChanged); + Cvar_RegisterVariable (&r_drawworld); + Cvar_RegisterVariable (&r_showtris); + Cvar_RegisterVariable (&r_showbboxes); + Cvar_RegisterVariable (&gl_farclip); + Cvar_RegisterVariable (&gl_fullbrights); + Cvar_RegisterVariable (&gl_overbright); + Cvar_SetCallback (&gl_fullbrights, GL_Fullbrights_f); + Cvar_SetCallback (&gl_overbright, GL_Overbright_f); + Cvar_RegisterVariable (&gl_overbright_models); + Cvar_RegisterVariable (&r_lerpmodels); + Cvar_RegisterVariable (&r_lerpmove); + Cvar_RegisterVariable (&r_nolerp_list); + Cvar_SetCallback (&r_nolerp_list, R_Model_ExtraFlags_List_f); + Cvar_RegisterVariable (&r_noshadow_list); + Cvar_SetCallback (&r_noshadow_list, R_Model_ExtraFlags_List_f); + //johnfitz + + Cvar_RegisterVariable (&gl_zfix); // QuakeSpasm z-fighting fix + Cvar_RegisterVariable (&r_lavaalpha); + Cvar_RegisterVariable (&r_telealpha); + Cvar_RegisterVariable (&r_slimealpha); + Cvar_RegisterVariable (&r_scale); + Cvar_SetCallback (&r_lavaalpha, R_SetLavaalpha_f); + Cvar_SetCallback (&r_telealpha, R_SetTelealpha_f); + Cvar_SetCallback (&r_slimealpha, R_SetSlimealpha_f); + + R_InitParticles (); + R_SetClearColor_f (&r_clearcolor); //johnfitz + + Sky_Init (); //johnfitz + Fog_Init (); //johnfitz +} + +/* +=============== +R_TranslatePlayerSkin -- johnfitz -- rewritten. also, only handles new colors, not new skins +=============== +*/ +void R_TranslatePlayerSkin (int playernum) +{ + int top, bottom; + + top = (cl.scores[playernum].colors & 0xf0)>>4; + bottom = cl.scores[playernum].colors &15; + + //FIXME: if gl_nocolors is on, then turned off, the textures may be out of sync with the scoreboard colors. + if (!gl_nocolors.value) + if (playertextures[playernum]) + TexMgr_ReloadImage (playertextures[playernum], top, bottom); +} + +/* +=============== +R_TranslateNewPlayerSkin -- johnfitz -- split off of TranslatePlayerSkin -- this is called when +the skin or model actually changes, instead of just new colors +added bug fix from bengt jardup +=============== +*/ +void R_TranslateNewPlayerSkin (int playernum) +{ + char name[64]; + byte *pixels; + aliashdr_t *paliashdr; + int skinnum; + +//get correct texture pixels + currententity = &cl_entities[1+playernum]; + + if (!currententity->model || currententity->model->type != mod_alias) + return; + + paliashdr = (aliashdr_t *)Mod_Extradata (currententity->model); + + skinnum = currententity->skinnum; + + //TODO: move these tests to the place where skinnum gets received from the server + if (skinnum < 0 || skinnum >= paliashdr->numskins) + { + Con_DPrintf("(%d): Invalid player skin #%d\n", playernum, skinnum); + skinnum = 0; + } + + pixels = (byte *)paliashdr + paliashdr->texels[skinnum]; // This is not a persistent place! + +//upload new image + q_snprintf(name, sizeof(name), "player_%i", playernum); + playertextures[playernum] = TexMgr_LoadImage (currententity->model, name, paliashdr->skinwidth, paliashdr->skinheight, + SRC_INDEXED, pixels, paliashdr->gltextures[skinnum][0]->source_file, paliashdr->gltextures[skinnum][0]->source_offset, TEXPREF_PAD | TEXPREF_OVERWRITE); + +//now recolor it + R_TranslatePlayerSkin (playernum); +} + +/* +=============== +R_NewGame -- johnfitz -- handle a game switch +=============== +*/ +void R_NewGame (void) +{ + int i; + + //clear playertexture pointers (the textures themselves were freed by texmgr_newgame) + for (i=0; ientities); + if (!data) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strcpy(key, com_token + 1); + else + strcpy(key, com_token); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_Parse(data); + if (!data) + return; // error + strcpy(value, com_token); + + if (!strcmp("wateralpha", key)) + map_wateralpha = atof(value); + + if (!strcmp("lavaalpha", key)) + map_lavaalpha = atof(value); + + if (!strcmp("telealpha", key)) + map_telealpha = atof(value); + + if (!strcmp("slimealpha", key)) + map_slimealpha = atof(value); + } +} + + +/* +=============== +R_NewMap +=============== +*/ +void R_NewMap (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + d_lightstylevalue[i] = 264; // normal light value + +// clear out efrags in case the level hasn't been reloaded +// FIXME: is this one short? + for (i=0 ; inumleafs ; i++) + cl.worldmodel->leafs[i].efrags = NULL; + + r_viewleaf = NULL; + R_ClearParticles (); + + GL_BuildLightmaps (); + GL_BuildBModelVertexBuffer (); + //ericw -- no longer load alias models into a VBO here, it's done in Mod_LoadAliasModel + + r_framecount = 0; //johnfitz -- paranoid? + r_visframecount = 0; //johnfitz -- paranoid? + + Sky_NewMap (); //johnfitz -- skybox in worldspawn + Fog_NewMap (); //johnfitz -- global fog in worldspawn + R_ParseWorldspawn (); //ericw -- wateralpha, lavaalpha, telealpha, slimealpha in worldspawn + + load_subdivide_size = gl_subdivide_size.value; //johnfitz -- is this the right place to set this? +} + +/* +==================== +R_TimeRefresh_f + +For program optimization +==================== +*/ +void R_TimeRefresh_f (void) +{ + int i; + float start, stop, time; + + if (cls.state != ca_connected) + { + Con_Printf("Not connected to a server\n"); + return; + } + + start = Sys_DoubleTime (); + for (i = 0; i < 128; i++) + { + GL_BeginRendering(&glx, &gly, &glwidth, &glheight); + r_refdef.viewangles[1] = i/128.0*360.0; + R_RenderView (); + GL_EndRendering (); + } + + glFinish (); + stop = Sys_DoubleTime (); + time = stop-start; + Con_Printf ("%f seconds (%f fps)\n", time, 128/time); +} + +void D_FlushCaches (void) +{ +} + +static GLuint gl_programs[16]; +static int gl_num_programs; + +static qboolean GL_CheckShader (GLuint shader) +{ + GLint status; + GL_GetShaderivFunc (shader, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + char infolog[1024]; + + memset(infolog, 0, sizeof(infolog)); + GL_GetShaderInfoLogFunc (shader, sizeof(infolog), NULL, infolog); + + Con_Warning ("GLSL program failed to compile: %s", infolog); + + return false; + } + return true; +} + +static qboolean GL_CheckProgram (GLuint program) +{ + GLint status; + GL_GetProgramivFunc (program, GL_LINK_STATUS, &status); + + if (status != GL_TRUE) + { + char infolog[1024]; + + memset(infolog, 0, sizeof(infolog)); + GL_GetProgramInfoLogFunc (program, sizeof(infolog), NULL, infolog); + + Con_Warning ("GLSL program failed to link: %s", infolog); + + return false; + } + return true; +} + +/* +============= +GL_GetUniformLocation +============= +*/ +GLint GL_GetUniformLocation (GLuint *programPtr, const char *name) +{ + GLint location; + + if (!programPtr) + return -1; + + location = GL_GetUniformLocationFunc(*programPtr, name); + if (location == -1) + { + Con_Warning("GL_GetUniformLocationFunc %s failed\n", name); + *programPtr = 0; + } + return location; +} + +/* +==================== +GL_CreateProgram + +Compiles and returns GLSL program. +==================== +*/ +GLuint GL_CreateProgram (const GLchar *vertSource, const GLchar *fragSource, int numbindings, const glsl_attrib_binding_t *bindings) +{ + int i; + GLuint program, vertShader, fragShader; + + if (!gl_glsl_able) + return 0; + + vertShader = GL_CreateShaderFunc (GL_VERTEX_SHADER); + GL_ShaderSourceFunc (vertShader, 1, &vertSource, NULL); + GL_CompileShaderFunc (vertShader); + if (!GL_CheckShader (vertShader)) + { + GL_DeleteShaderFunc (vertShader); + return 0; + } + + fragShader = GL_CreateShaderFunc (GL_FRAGMENT_SHADER); + GL_ShaderSourceFunc (fragShader, 1, &fragSource, NULL); + GL_CompileShaderFunc (fragShader); + if (!GL_CheckShader (fragShader)) + { + GL_DeleteShaderFunc (vertShader); + GL_DeleteShaderFunc (fragShader); + return 0; + } + + program = GL_CreateProgramFunc (); + GL_AttachShaderFunc (program, vertShader); + GL_DeleteShaderFunc (vertShader); + GL_AttachShaderFunc (program, fragShader); + GL_DeleteShaderFunc (fragShader); + + for (i = 0; i < numbindings; i++) + { + GL_BindAttribLocationFunc (program, bindings[i].attrib, bindings[i].name); + } + + GL_LinkProgramFunc (program); + + if (!GL_CheckProgram (program)) + { + GL_DeleteProgramFunc (program); + return 0; + } + else + { + if (gl_num_programs == (sizeof(gl_programs)/sizeof(GLuint))) + Host_Error ("gl_programs overflow"); + + gl_programs[gl_num_programs] = program; + gl_num_programs++; + + return program; + } +} + +/* +==================== +R_DeleteShaders + +Deletes any GLSL programs that have been created. +==================== +*/ +void R_DeleteShaders (void) +{ + int i; + + if (!gl_glsl_able) + return; + + for (i = 0; i < gl_num_programs; i++) + { + GL_DeleteProgramFunc (gl_programs[i]); + gl_programs[i] = 0; + } + gl_num_programs = 0; +} +GLuint current_array_buffer, current_element_array_buffer; + +/* +==================== +GL_BindBuffer + +glBindBuffer wrapper +==================== +*/ +void GL_BindBuffer (GLenum target, GLuint buffer) +{ + GLuint *cache; + + if (!gl_vbo_able) + return; + + switch (target) + { + case GL_ARRAY_BUFFER: + cache = ¤t_array_buffer; + break; + case GL_ELEMENT_ARRAY_BUFFER: + cache = ¤t_element_array_buffer; + break; + default: + Host_Error("GL_BindBuffer: unsupported target %d", (int)target); + return; + } + + if (*cache != buffer) + { + *cache = buffer; + GL_BindBufferFunc (target, *cache); + } +} + +/* +==================== +GL_ClearBufferBindings + +This must be called if you do anything that could make the cached bindings +invalid (e.g. manually binding, destroying the context). +==================== +*/ +void GL_ClearBufferBindings () +{ + if (!gl_vbo_able) + return; + + current_array_buffer = 0; + current_element_array_buffer = 0; + GL_BindBufferFunc (GL_ARRAY_BUFFER, 0); + GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, 0); +} diff --git a/source/gl_screen.c b/source/gl_screen.c new file mode 100644 index 0000000..c733353 --- /dev/null +++ b/source/gl_screen.c @@ -0,0 +1,1133 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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.c -- master for refresh, status bar, console, chat, notify, etc + +#include "quakedef.h" + +/* + +background clear +rendering +turtle/net/ram icons +sbar +centerprint / slow centerprint +notify lines +intermission / finale overlay +loading plaque +console +menu + +required background clears +required update regions + + +syncronous draw mode or async +One off screen buffer, with updates either copied or xblited +Need to double buffer? + + +async draw will require the refresh area to be cleared, because it will be +xblited, but sync draw can just ignore it. + +sync +draw + +CenterPrint () +SlowPrint () +Screen_Update (); +Con_Printf (); + +net +turn off messages option + +the refresh is allways rendered, unless the console is full screen + + +console is: + notify lines + half + full + +*/ + + +int glx, gly, glwidth, glheight; + +float scr_con_current; +float scr_conlines; // lines of console to display + +//johnfitz -- new cvars +cvar_t scr_menuscale = {"scr_menuscale", "1", CVAR_ARCHIVE}; +cvar_t scr_sbarscale = {"scr_sbarscale", "1", CVAR_ARCHIVE}; +cvar_t scr_sbaralpha = {"scr_sbaralpha", "0.75", CVAR_ARCHIVE}; +cvar_t scr_conwidth = {"scr_conwidth", "0", CVAR_ARCHIVE}; +cvar_t scr_conscale = {"scr_conscale", "1", CVAR_ARCHIVE}; +cvar_t scr_crosshairscale = {"scr_crosshairscale", "1", CVAR_ARCHIVE}; +cvar_t scr_showfps = {"scr_showfps", "0", CVAR_NONE}; +cvar_t scr_clock = {"scr_clock", "0", CVAR_NONE}; +//johnfitz + +cvar_t scr_viewsize = {"viewsize","100", CVAR_ARCHIVE}; +cvar_t scr_fov = {"fov","90",CVAR_NONE}; // 10 - 170 +cvar_t scr_fov_adapt = {"fov_adapt","1",CVAR_ARCHIVE}; +cvar_t scr_conspeed = {"scr_conspeed","500",CVAR_ARCHIVE}; +cvar_t scr_centertime = {"scr_centertime","2",CVAR_NONE}; +cvar_t scr_showram = {"showram","1",CVAR_NONE}; +cvar_t scr_showturtle = {"showturtle","0",CVAR_NONE}; +cvar_t scr_showpause = {"showpause","1",CVAR_NONE}; +cvar_t scr_printspeed = {"scr_printspeed","8",CVAR_NONE}; +cvar_t gl_triplebuffer = {"gl_triplebuffer", "1", CVAR_ARCHIVE}; + +extern cvar_t crosshair; + +qboolean scr_initialized; // ready to draw + +qpic_t *scr_ram; +qpic_t *scr_net; +qpic_t *scr_turtle; + +int clearconsole; +int clearnotify; + +vrect_t scr_vrect; + +qboolean scr_disabled_for_loading; +qboolean scr_drawloading; +float scr_disabled_time; + +int scr_tileclear_updates = 0; //johnfitz + +void SCR_ScreenShot_f (void); + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + +char scr_centerstring[1024]; +float scr_centertime_start; // for slow victory printing +float scr_centertime_off; +int scr_center_lines; +int scr_erase_lines; +int scr_erase_center; + +/* +============== +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) //update centerprint data +{ + strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1); + scr_centertime_off = scr_centertime.value; + scr_centertime_start = cl.time; + +// count the number of lines for centering + scr_center_lines = 1; + str = scr_centerstring; + while (*str) + { + if (*str == '\n') + scr_center_lines++; + str++; + } +} + +void SCR_DrawCenterString (void) //actually do the drawing +{ + char *start; + int l; + int j; + int x, y; + int remaining; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + +// the finale prints the characters one at a time + if (cl.intermission) + remaining = scr_printspeed.value * (cl.time - scr_centertime_start); + else + remaining = 9999; + + scr_erase_center = 0; + start = scr_centerstring; + + if (scr_center_lines <= 4) + y = 200*0.35; //johnfitz -- 320x200 coordinate system + else + y = 48; + if (crosshair.value) + y -= 8; + + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (start[l] == '\n' || !start[l]) + break; + x = (320 - l*8)/2; //johnfitz -- 320x200 coordinate system + for (j=0 ; j scr_erase_lines) + scr_erase_lines = scr_center_lines; + + scr_centertime_off -= host_frametime; + + if (scr_centertime_off <= 0 && !cl.intermission) + return; + if (key_dest != key_game) + return; + if (cl.paused) //johnfitz -- don't show centerprint during a pause + return; + + SCR_DrawCenterString (); +} + +//============================================================================= + +/* +==================== +AdaptFovx +Adapt a 4:3 horizontal FOV to the current screen size using the "Hor+" scaling: +2.0 * atan(width / height * 3.0 / 4.0 * tan(fov_x / 2.0)) +==================== +*/ +float AdaptFovx (float fov_x, float width, float height) +{ + float a, x; + + if (fov_x < 1 || fov_x > 179) + Sys_Error ("Bad fov: %f", fov_x); + + if (!scr_fov_adapt.value) + return fov_x; + if ((x = height / width) == 0.75) + return fov_x; + a = atan(0.75 / x * tan(fov_x / 360 * M_PI)); + a = a * 360 / M_PI; + return a; +} + +/* +==================== +CalcFovy +==================== +*/ +float CalcFovy (float fov_x, float width, float height) +{ + float a, x; + + if (fov_x < 1 || fov_x > 179) + Sys_Error ("Bad fov: %f", fov_x); + + x = width / tan(fov_x / 360 * M_PI); + a = atan(height / x); + a = a * 360 / M_PI; + return a; +} + +/* +================= +SCR_CalcRefdef + +Must be called whenever vid changes +Internal use only +================= +*/ +static void SCR_CalcRefdef (void) +{ + float size, scale; //johnfitz -- scale + +// force the status bar to redraw + Sbar_Changed (); + + scr_tileclear_updates = 0; //johnfitz + +// bound viewsize + if (scr_viewsize.value < 30) + Cvar_SetQuick (&scr_viewsize, "30"); + if (scr_viewsize.value > 120) + Cvar_SetQuick (&scr_viewsize, "120"); + +// bound fov + if (scr_fov.value < 10) + Cvar_SetQuick (&scr_fov, "10"); + if (scr_fov.value > 170) + Cvar_SetQuick (&scr_fov, "170"); + + vid.recalc_refdef = 0; + + //johnfitz -- rewrote this section + size = scr_viewsize.value; + scale = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + + if (size >= 120 || cl.intermission || scr_sbaralpha.value < 1) //johnfitz -- scr_sbaralpha.value + sb_lines = 0; + else if (size >= 110) + sb_lines = 24 * scale; + else + sb_lines = 48 * scale; + + size = q_min(scr_viewsize.value, 100) / 100; + //johnfitz + + //johnfitz -- rewrote this section + r_refdef.vrect.width = q_max(glwidth * size, 96); //no smaller than 96, for icons + r_refdef.vrect.height = q_min(glheight * size, glheight - sb_lines); //make room for sbar + r_refdef.vrect.x = (glwidth - r_refdef.vrect.width)/2; + r_refdef.vrect.y = (glheight - sb_lines - r_refdef.vrect.height)/2; + //johnfitz + + r_refdef.fov_x = AdaptFovx(scr_fov.value, vid.width, vid.height); + r_refdef.fov_y = CalcFovy (r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height); + + scr_vrect = r_refdef.vrect; +} + + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +void SCR_SizeUp_f (void) +{ + Cvar_SetValueQuick (&scr_viewsize, scr_viewsize.value+10); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +void SCR_SizeDown_f (void) +{ + Cvar_SetValueQuick (&scr_viewsize, scr_viewsize.value-10); +} + +static void SCR_Callback_refdef (cvar_t *var) +{ + vid.recalc_refdef = 1; +} + +/* +================== +SCR_Conwidth_f -- johnfitz -- called when scr_conwidth or scr_conscale changes +================== +*/ +void SCR_Conwidth_f (cvar_t *var) +{ + vid.recalc_refdef = 1; + vid.conwidth = (scr_conwidth.value > 0) ? (int)scr_conwidth.value : (scr_conscale.value > 0) ? (int)(vid.width/scr_conscale.value) : vid.width; + vid.conwidth = CLAMP (320, vid.conwidth, vid.width); + vid.conwidth &= 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; +} + +//============================================================================ + +/* +================== +SCR_LoadPics -- johnfitz +================== +*/ +void SCR_LoadPics (void) +{ + scr_ram = Draw_PicFromWad ("ram"); + scr_net = Draw_PicFromWad ("net"); + scr_turtle = Draw_PicFromWad ("turtle"); +} + +/* +================== +SCR_Init +================== +*/ +void SCR_Init (void) +{ + //johnfitz -- new cvars + Cvar_RegisterVariable (&scr_menuscale); + Cvar_RegisterVariable (&scr_sbarscale); + Cvar_SetCallback (&scr_sbaralpha, SCR_Callback_refdef); + Cvar_RegisterVariable (&scr_sbaralpha); + Cvar_SetCallback (&scr_conwidth, &SCR_Conwidth_f); + Cvar_SetCallback (&scr_conscale, &SCR_Conwidth_f); + Cvar_RegisterVariable (&scr_conwidth); + Cvar_RegisterVariable (&scr_conscale); + Cvar_RegisterVariable (&scr_crosshairscale); + Cvar_RegisterVariable (&scr_showfps); + Cvar_RegisterVariable (&scr_clock); + //johnfitz + Cvar_SetCallback (&scr_fov, SCR_Callback_refdef); + Cvar_SetCallback (&scr_fov_adapt, SCR_Callback_refdef); + Cvar_SetCallback (&scr_viewsize, SCR_Callback_refdef); + Cvar_RegisterVariable (&scr_fov); + Cvar_RegisterVariable (&scr_fov_adapt); + Cvar_RegisterVariable (&scr_viewsize); + Cvar_RegisterVariable (&scr_conspeed); + Cvar_RegisterVariable (&scr_showram); + Cvar_RegisterVariable (&scr_showturtle); + Cvar_RegisterVariable (&scr_showpause); + Cvar_RegisterVariable (&scr_centertime); + Cvar_RegisterVariable (&scr_printspeed); + Cvar_RegisterVariable (&gl_triplebuffer); + + Cmd_AddCommand ("screenshot",SCR_ScreenShot_f); + Cmd_AddCommand ("sizeup",SCR_SizeUp_f); + Cmd_AddCommand ("sizedown",SCR_SizeDown_f); + + SCR_LoadPics (); //johnfitz + + scr_initialized = true; +} + +//============================================================================ + +/* +============== +SCR_DrawFPS -- johnfitz +============== +*/ +void SCR_DrawFPS (void) +{ + static double oldtime = 0; + static double lastfps = 0; + static int oldframecount = 0; + double elapsed_time; + int frames; + + elapsed_time = realtime - oldtime; + frames = r_framecount - oldframecount; + + if (elapsed_time < 0 || frames < 0) + { + oldtime = realtime; + oldframecount = r_framecount; + return; + } + // update value every 3/4 second + if (elapsed_time > 0.75) + { + lastfps = frames / elapsed_time; + oldtime = realtime; + oldframecount = r_framecount; + } + + if (scr_showfps.value) + { + char st[16]; + int x, y; + sprintf (st, "%4.0f fps", lastfps); + x = 320 - (strlen(st)<<3); + y = 200 - 8; + if (scr_clock.value) y -= 8; //make room for clock + GL_SetCanvas (CANVAS_BOTTOMRIGHT); + Draw_String (x, y, st); + scr_tileclear_updates = 0; + } +} + +/* +============== +SCR_DrawClock -- johnfitz +============== +*/ +void SCR_DrawClock (void) +{ + char str[12]; + + if (scr_clock.value == 1) + { + int minutes, seconds; + + minutes = cl.time / 60; + seconds = ((int)cl.time)%60; + + sprintf (str,"%i:%i%i", minutes, seconds/10, seconds%10); + } + else + return; + + //draw it + GL_SetCanvas (CANVAS_BOTTOMRIGHT); + Draw_String (320 - (strlen(str)<<3), 200 - 8, str); + + scr_tileclear_updates = 0; +} + +/* +============== +SCR_DrawDevStats +============== +*/ +void SCR_DrawDevStats (void) +{ + char str[40]; + int y = 25-9; //9=number of lines to print + int x = 0; //margin + + if (!devstats.value) + return; + + GL_SetCanvas (CANVAS_BOTTOMLEFT); + + Draw_Fill (x, y*8, 19*8, 9*8, 0, 0.5); //dark rectangle + + sprintf (str, "devstats |Curr Peak"); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "---------+---------"); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Edicts |%4i %4i", dev_stats.edicts, dev_peakstats.edicts); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Packet |%4i %4i", dev_stats.packetsize, dev_peakstats.packetsize); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Visedicts|%4i %4i", dev_stats.visedicts, dev_peakstats.visedicts); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Efrags |%4i %4i", dev_stats.efrags, dev_peakstats.efrags); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Dlights |%4i %4i", dev_stats.dlights, dev_peakstats.dlights); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Beams |%4i %4i", dev_stats.beams, dev_peakstats.beams); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Tempents |%4i %4i", dev_stats.tempents, dev_peakstats.tempents); + Draw_String (x, (y++)*8-x, str); +} + +/* +============== +SCR_DrawRam +============== +*/ +void SCR_DrawRam (void) +{ + if (!scr_showram.value) + return; + + if (!r_cache_thrash) + return; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + Draw_Pic (scr_vrect.x+32, scr_vrect.y, scr_ram); +} + +/* +============== +SCR_DrawTurtle +============== +*/ +void SCR_DrawTurtle (void) +{ + static int count; + + if (!scr_showturtle.value) + return; + + if (host_frametime < 0.1) + { + count = 0; + return; + } + + count++; + if (count < 3) + return; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + Draw_Pic (scr_vrect.x, scr_vrect.y, scr_turtle); +} + +/* +============== +SCR_DrawNet +============== +*/ +void SCR_DrawNet (void) +{ + if (realtime - cl.last_received_message < 0.3) + return; + if (cls.demoplayback) + return; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + Draw_Pic (scr_vrect.x+64, scr_vrect.y, scr_net); +} + +/* +============== +DrawPause +============== +*/ +void SCR_DrawPause (void) +{ + qpic_t *pic; + + if (!cl.paused) + return; + + if (!scr_showpause.value) // turn off for screenshots + return; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/pause.lmp"); + Draw_Pic ( (320 - pic->width)/2, (240 - 48 - pic->height)/2, pic); //johnfitz -- stretched menus + + scr_tileclear_updates = 0; //johnfitz +} + +/* +============== +SCR_DrawLoading +============== +*/ +void SCR_DrawLoading (void) +{ + qpic_t *pic; + + if (!scr_drawloading) + return; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/loading.lmp"); + Draw_Pic ( (320 - pic->width)/2, (240 - 48 - pic->height)/2, pic); //johnfitz -- stretched menus + + scr_tileclear_updates = 0; //johnfitz +} + +/* +============== +SCR_DrawCrosshair -- johnfitz +============== +*/ +void SCR_DrawCrosshair (void) +{ + if (!crosshair.value) + return; + + GL_SetCanvas (CANVAS_CROSSHAIR); + Draw_Character (-4, -4, '+'); //0,0 is center of viewport +} + + + +//============================================================================= + + +/* +================== +SCR_SetUpToDrawConsole +================== +*/ +void SCR_SetUpToDrawConsole (void) +{ + //johnfitz -- let's hack away the problem of slow console when host_timescale is <0 + extern cvar_t host_timescale; + float timescale; + //johnfitz + + Con_CheckResize (); + + if (scr_drawloading) + return; // never a console with loading plaque + +// decide on the height of the console + con_forcedup = !cl.worldmodel || cls.signon != SIGNONS; + + if (con_forcedup) + { + scr_conlines = glheight; //full screen //johnfitz -- glheight instead of vid.height + scr_con_current = scr_conlines; + } + else if (key_dest == key_console) + scr_conlines = glheight/2; //half screen //johnfitz -- glheight instead of vid.height + else + scr_conlines = 0; //none visible + + timescale = (host_timescale.value > 0) ? host_timescale.value : 1; //johnfitz -- timescale + + if (scr_conlines < scr_con_current) + { + // ericw -- (glheight/600.0) factor makes conspeed resolution independent, using 800x600 as a baseline + scr_con_current -= scr_conspeed.value*(glheight/600.0)*host_frametime/timescale; //johnfitz -- timescale + if (scr_conlines > scr_con_current) + scr_con_current = scr_conlines; + } + else if (scr_conlines > scr_con_current) + { + // ericw -- (glheight/600.0) + scr_con_current += scr_conspeed.value*(glheight/600.0)*host_frametime/timescale; //johnfitz -- timescale + if (scr_conlines < scr_con_current) + scr_con_current = scr_conlines; + } + + if (clearconsole++ < vid.numpages) + Sbar_Changed (); + + if (!con_forcedup && scr_con_current) + scr_tileclear_updates = 0; //johnfitz +} + +/* +================== +SCR_DrawConsole +================== +*/ +void SCR_DrawConsole (void) +{ + if (scr_con_current) + { + Con_DrawConsole (scr_con_current, true); + clearconsole = 0; + } + else + { + if (key_dest == key_game || key_dest == key_message) + Con_DrawNotify (); // only draw notify in game + } +} + + +/* +============================================================================== + +SCREEN SHOTS + +============================================================================== +*/ + +static void SCR_ScreenShot_Usage (void) +{ + Con_Printf ("usage: screenshot \n"); + Con_Printf (" format must be \"png\" or \"tga\" or \"jpg\"\n"); + Con_Printf (" quality must be 1-100\n"); + return; +} + +/* +================== +SCR_ScreenShot_f -- johnfitz -- rewritten to use Image_WriteTGA +================== +*/ +void SCR_ScreenShot_f (void) +{ + byte *buffer; + char ext[4]; + char imagename[16]; //johnfitz -- was [80] + char checkname[MAX_OSPATH]; + int i, quality; + qboolean ok; + + Q_strncpy (ext, "png", sizeof(ext)); + + if (Cmd_Argc () >= 2) + { + const char *requested_ext = Cmd_Argv (1); + + if (!q_strcasecmp ("png", requested_ext) + || !q_strcasecmp ("tga", requested_ext) + || !q_strcasecmp ("jpg", requested_ext)) + Q_strncpy (ext, requested_ext, sizeof(ext)); + else + { + SCR_ScreenShot_Usage (); + return; + } + } + +// read quality as the 3rd param (only used for JPG) + quality = 90; + if (Cmd_Argc () >= 3) + quality = Q_atoi (Cmd_Argv(2)); + if (quality < 1 || quality > 100) + { + SCR_ScreenShot_Usage (); + return; + } + +// find a file name to save it to + for (i=0; i<10000; i++) + { + q_snprintf (imagename, sizeof(imagename), "spasm%04i.%s", i, ext); // "fitz%04i.tga" + q_snprintf (checkname, sizeof(checkname), "%s/%s", com_gamedir, imagename); + if (Sys_FileTime(checkname) == -1) + break; // file doesn't exist + } + if (i == 10000) + { + Con_Printf ("SCR_ScreenShot_f: Couldn't find an unused filename\n"); + return; + } + +//get data + if (!(buffer = (byte *) malloc(glwidth*glheight*3))) + { + Con_Printf ("SCR_ScreenShot_f: Couldn't allocate memory\n"); + return; + } + + glPixelStorei (GL_PACK_ALIGNMENT, 1);/* for widths that aren't a multiple of 4 */ + glReadPixels (glx, gly, glwidth, glheight, GL_RGB, GL_UNSIGNED_BYTE, buffer); + +// now write the file + if (!q_strncasecmp (ext, "png", sizeof(ext))) + ok = Image_WritePNG (imagename, buffer, glwidth, glheight, 24, false); + else if (!q_strncasecmp (ext, "tga", sizeof(ext))) + ok = Image_WriteTGA (imagename, buffer, glwidth, glheight, 24, false); + else if (!q_strncasecmp (ext, "jpg", sizeof(ext))) + ok = Image_WriteJPG (imagename, buffer, glwidth, glheight, 24, quality, false); + else + ok = false; + + if (ok) + Con_Printf ("Wrote %s\n", imagename); + else + Con_Printf ("SCR_ScreenShot_f: Couldn't create %s\n", imagename); + + free (buffer); +} + + +//============================================================================= + + +/* +=============== +SCR_BeginLoadingPlaque + +================ +*/ +void SCR_BeginLoadingPlaque (void) +{ + S_StopAllSounds (true); + + if (cls.state != ca_connected) + return; + if (cls.signon != SIGNONS) + return; + +// redraw with no console and the loading plaque + Con_ClearNotify (); + scr_centertime_off = 0; + scr_con_current = 0; + + scr_drawloading = true; + Sbar_Changed (); + SCR_UpdateScreen (); + scr_drawloading = false; + + scr_disabled_for_loading = true; + scr_disabled_time = realtime; +} + +/* +=============== +SCR_EndLoadingPlaque + +================ +*/ +void SCR_EndLoadingPlaque (void) +{ + scr_disabled_for_loading = false; + Con_ClearNotify (); +} + +//============================================================================= + +const char *scr_notifystring; +qboolean scr_drawdialog; + +void SCR_DrawNotifyString (void) +{ + const char *start; + int l; + int j; + int x, y; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + start = scr_notifystring; + + y = 200 * 0.35; //johnfitz -- stretched overlays + + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (start[l] == '\n' || !start[l]) + break; + x = (320 - l*8)/2; //johnfitz -- stretched overlays + for (j=0 ; j time1) + return false; + //johnfitz + + return (lastchar == 'y' || lastchar == 'Y' || lastkey == K_ABUTTON); +} + + +//============================================================================= + +//johnfitz -- deleted SCR_BringDownConsole + + +/* +================== +SCR_TileClear +johnfitz -- modified to use glwidth/glheight instead of vid.width/vid.height + also fixed the dimentions of right and top panels + also added scr_tileclear_updates +================== +*/ +void SCR_TileClear (void) +{ + //ericw -- added check for glsl gamma. TODO: remove this ugly optimization? + if (scr_tileclear_updates >= vid.numpages && !gl_clear.value && !(gl_glsl_gamma_able && vid_gamma.value != 1)) + return; + scr_tileclear_updates++; + + if (r_refdef.vrect.x > 0) + { + // left + Draw_TileClear (0, + 0, + r_refdef.vrect.x, + glheight - sb_lines); + // right + Draw_TileClear (r_refdef.vrect.x + r_refdef.vrect.width, + 0, + glwidth - r_refdef.vrect.x - r_refdef.vrect.width, + glheight - sb_lines); + } + + if (r_refdef.vrect.y > 0) + { + // top + Draw_TileClear (r_refdef.vrect.x, + 0, + r_refdef.vrect.width, + r_refdef.vrect.y); + // bottom + Draw_TileClear (r_refdef.vrect.x, + r_refdef.vrect.y + r_refdef.vrect.height, + r_refdef.vrect.width, + glheight - r_refdef.vrect.y - r_refdef.vrect.height - sb_lines); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. + +WARNING: be very careful calling this from elsewhere, because the refresh +needs almost the entire 256k of stack space! +================== +*/ +void SCR_UpdateScreen (void) +{ + vid.numpages = (gl_triplebuffer.value) ? 3 : 2; + + if (scr_disabled_for_loading) + { + if (realtime - scr_disabled_time > 60) + { + scr_disabled_for_loading = false; + Con_Printf ("load failed.\n"); + } + else + return; + } + + if (!scr_initialized || !con_initialized) + return; // not initialized yet + + + GL_BeginRendering (&glx, &gly, &glwidth, &glheight); + + // + // determine size of refresh window + // + if (vid.recalc_refdef) + SCR_CalcRefdef (); + +// +// do 3D refresh drawing, and then update the screen +// + SCR_SetUpToDrawConsole (); + + V_RenderView (); + + GL_Set2D (); + + //FIXME: only call this when needed + SCR_TileClear (); + + if (scr_drawdialog) //new game confirm + { + if (con_forcedup) + Draw_ConsoleBackground (); + else + Sbar_Draw (); + Draw_FadeScreen (); + SCR_DrawNotifyString (); + } + else if (scr_drawloading) //loading + { + SCR_DrawLoading (); + Sbar_Draw (); + } + else if (cl.intermission == 1 && key_dest == key_game) //end of level + { + Sbar_IntermissionOverlay (); + } + else if (cl.intermission == 2 && key_dest == key_game) //end of episode + { + Sbar_FinaleOverlay (); + SCR_CheckDrawCenterString (); + } + else + { + SCR_DrawCrosshair (); //johnfitz + SCR_DrawRam (); + SCR_DrawNet (); + SCR_DrawTurtle (); + SCR_DrawPause (); + SCR_CheckDrawCenterString (); + Sbar_Draw (); + SCR_DrawDevStats (); //johnfitz + SCR_DrawFPS (); //johnfitz + SCR_DrawClock (); //johnfitz + SCR_DrawConsole (); + M_Draw (); + } + + V_UpdateBlend (); //johnfitz -- V_UpdatePalette cleaned up and renamed + + GLSLGamma_GammaCorrect (); + + GL_EndRendering (); +} + diff --git a/source/gl_sky.c b/source/gl_sky.c new file mode 100644 index 0000000..7c9cf63 --- /dev/null +++ b/source/gl_sky.c @@ -0,0 +1,1029 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +//gl_sky.c + +#include "quakedef.h" + +#define MAX_CLIP_VERTS 64 + +float Fog_GetDensity(void); +float *Fog_GetColor(void); + +extern qmodel_t *loadmodel; +extern int rs_skypolys; //for r_speeds readout +extern int rs_skypasses; //for r_speeds readout +float skyflatcolor[3]; +float skymins[2][6], skymaxs[2][6]; + +char skybox_name[32] = ""; //name of current skybox, or "" if no skybox + +gltexture_t *skybox_textures[6]; +gltexture_t *solidskytexture, *alphaskytexture; + +extern cvar_t gl_farclip; +cvar_t r_fastsky = {"r_fastsky", "0", CVAR_NONE}; +cvar_t r_sky_quality = {"r_sky_quality", "12", CVAR_NONE}; +cvar_t r_skyalpha = {"r_skyalpha", "1", CVAR_NONE}; +cvar_t r_skyfog = {"r_skyfog","0.5",CVAR_NONE}; + +int skytexorder[6] = {0,2,1,3,4,5}; //for skybox + +vec3_t skyclip[6] = { + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +int st_to_vec[6][3] = +{ + {3,-1,2}, + {-3,1,2}, + {1,3,2}, + {-1,-3,2}, + {-2,-1,3}, // straight up + {2,-1,-3} // straight down +}; + +int vec_to_st[6][3] = +{ + {-2,3,1}, + {2,3,-1}, + {1,3,2}, + {-1,3,-2}, + {-2,-1,3}, + {-2,1,-3} +}; + +float skyfog; // ericw + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============= +Sky_LoadTexture + +A sky texture is 256*128, with the left side being a masked overlay +============== +*/ +void Sky_LoadTexture (texture_t *mt) +{ + char texturename[64]; + int i, j, p, r, g, b, count; + byte *src; + static byte front_data[128*128]; //FIXME: Hunk_Alloc + static byte back_data[128*128]; //FIXME: Hunk_Alloc + unsigned *rgba; + + src = (byte *)mt + mt->offsets[0]; + +// extract back layer and upload + for (i=0 ; i<128 ; i++) + for (j=0 ; j<128 ; j++) + back_data[(i*128) + j] = src[i*256 + j + 128]; + + q_snprintf(texturename, sizeof(texturename), "%s:%s_back", loadmodel->name, mt->name); + solidskytexture = TexMgr_LoadImage (loadmodel, texturename, 128, 128, SRC_INDEXED, back_data, "", (src_offset_t)back_data, TEXPREF_NONE); + +// extract front layer and upload + for (i=0 ; i<128 ; i++) + for (j=0 ; j<128 ; j++) + { + front_data[(i*128) + j] = src[i*256 + j]; + if (front_data[(i*128) + j] == 0) + front_data[(i*128) + j] = 255; + } + + q_snprintf(texturename, sizeof(texturename), "%s:%s_front", loadmodel->name, mt->name); + alphaskytexture = TexMgr_LoadImage (loadmodel, texturename, 128, 128, SRC_INDEXED, front_data, "", (src_offset_t)front_data, TEXPREF_ALPHA); + +// calculate r_fastsky color based on average of all opaque foreground colors + r = g = b = count = 0; + for (i=0 ; i<128 ; i++) + for (j=0 ; j<128 ; j++) + { + p = src[i*256 + j]; + if (p != 0) + { + rgba = &d_8to24table[p]; + r += ((byte *)rgba)[0]; + g += ((byte *)rgba)[1]; + b += ((byte *)rgba)[2]; + count++; + } + } + skyflatcolor[0] = (float)r/(count*255); + skyflatcolor[1] = (float)g/(count*255); + skyflatcolor[2] = (float)b/(count*255); +} + +/* +================== +Sky_LoadSkyBox +================== +*/ +const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; +void Sky_LoadSkyBox (const char *name) +{ + int i, mark, width, height; + char filename[MAX_OSPATH]; + byte *data; + qboolean nonefound = true; + + if (strcmp(skybox_name, name) == 0) + return; //no change + + //purge old textures + for (i=0; i<6; i++) + { + if (skybox_textures[i] && skybox_textures[i] != notexture) + TexMgr_FreeTexture (skybox_textures[i]); + skybox_textures[i] = NULL; + } + + //turn off skybox if sky is set to "" + if (name[0] == 0) + { + skybox_name[0] = 0; + return; + } + + //load textures + for (i=0; i<6; i++) + { + mark = Hunk_LowMark (); + q_snprintf (filename, sizeof(filename), "gfx/env/%s%s", name, suf[i]); + data = Image_LoadImage (filename, &width, &height); + if (data) + { + skybox_textures[i] = TexMgr_LoadImage (cl.worldmodel, filename, width, height, SRC_RGBA, data, filename, 0, TEXPREF_NONE); + nonefound = false; + } + else + { + Con_Printf ("Couldn't load %s\n", filename); + skybox_textures[i] = notexture; + } + Hunk_FreeToLowMark (mark); + } + + if (nonefound) // go back to scrolling sky if skybox is totally missing + { + for (i=0; i<6; i++) + { + if (skybox_textures[i] && skybox_textures[i] != notexture) + TexMgr_FreeTexture (skybox_textures[i]); + skybox_textures[i] = NULL; + } + skybox_name[0] = 0; + return; + } + + strcpy(skybox_name, name); +} + +/* +================= +Sky_NewMap +================= +*/ +void Sky_NewMap (void) +{ + char key[128], value[4096]; + const char *data; + int i; + + // + // initially no sky + // + skybox_name[0] = 0; + for (i=0; i<6; i++) + skybox_textures[i] = NULL; + skyfog = r_skyfog.value; + + // + // read worldspawn (this is so ugly, and shouldn't it be done on the server?) + // + data = cl.worldmodel->entities; + if (!data) + return; //FIXME: how could this possibly ever happen? -- if there's no + // worldspawn then the sever wouldn't send the loadmap message to the client + + data = COM_Parse(data); + if (!data) //should never happen + return; // error + if (com_token[0] != '{') //should never happen + return; // error + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strcpy(key, com_token + 1); + else + strcpy(key, com_token); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_Parse(data); + if (!data) + return; // error + strcpy(value, com_token); + + if (!strcmp("sky", key)) + Sky_LoadSkyBox(value); + + if (!strcmp("skyfog", key)) + skyfog = atof(value); + +#if 1 //also accept non-standard keys + else if (!strcmp("skyname", key)) //half-life + Sky_LoadSkyBox(value); + else if (!strcmp("qlsky", key)) //quake lives + Sky_LoadSkyBox(value); +#endif + } +} + +/* +================= +Sky_SkyCommand_f +================= +*/ +void Sky_SkyCommand_f (void) +{ + switch (Cmd_Argc()) + { + case 1: + Con_Printf("\"sky\" is \"%s\"\n", skybox_name); + break; + case 2: + Sky_LoadSkyBox(Cmd_Argv(1)); + break; + default: + Con_Printf("usage: sky \n"); + } +} + +/* +==================== +R_SetSkyfog_f -- ericw +==================== +*/ +static void R_SetSkyfog_f (cvar_t *var) +{ +// clear any skyfog setting from worldspawn + skyfog = var->value; +} + +/* +============= +Sky_Init +============= +*/ +void Sky_Init (void) +{ + int i; + + Cvar_RegisterVariable (&r_fastsky); + Cvar_RegisterVariable (&r_sky_quality); + Cvar_RegisterVariable (&r_skyalpha); + Cvar_RegisterVariable (&r_skyfog); + Cvar_SetCallback (&r_skyfog, R_SetSkyfog_f); + + Cmd_AddCommand ("sky",Sky_SkyCommand_f); + + for (i=0; i<6; i++) + skybox_textures[i] = NULL; +} + +//============================================================================== +// +// PROCESS SKY SURFS +// +//============================================================================== + +/* +================= +Sky_ProjectPoly + +update sky bounds +================= +*/ +void Sky_ProjectPoly (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < skymins[0][axis]) + skymins[0][axis] = s; + if (t < skymins[1][axis]) + skymins[1][axis] = t; + if (s > skymaxs[0][axis]) + skymaxs[0][axis] = s; + if (t > skymaxs[1][axis]) + skymaxs[1][axis] = t; + } +} + +/* +================= +Sky_ClipPoly +================= +*/ +void Sky_ClipPoly (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + qboolean front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + Sys_Error ("Sky_ClipPoly: MAX_CLIP_VERTS"); + if (stage == 6) // fully clipped + { + Sky_ProjectPoly (nump, vecs); + return; + } + + front = back = false; + norm = skyclip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if (d < ON_EPSILON) + { + back = true; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + Sky_ClipPoly (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumverts ; i++) + VectorSubtract (p->verts[i], r_origin, verts[i]); + Sky_ClipPoly (p->numverts, verts[0], 0); + } +} + +/* +================ +Sky_ProcessTextureChains -- handles sky polys in world model +================ +*/ +void Sky_ProcessTextureChains (void) +{ + int i; + msurface_t *s; + texture_t *t; + + if (!r_drawworld_cheatsafe) + return; + + for (i=0 ; inumtextures ; i++) + { + t = cl.worldmodel->textures[i]; + + if (!t || !t->texturechains[chain_world] || !(t->texturechains[chain_world]->flags & SURF_DRAWSKY)) + continue; + + for (s = t->texturechains[chain_world]; s; s = s->texturechain) + if (!s->culled) + Sky_ProcessPoly (s->polys); + } +} + +/* +================ +Sky_ProcessEntities -- handles sky polys on brush models +================ +*/ +void Sky_ProcessEntities (void) +{ + entity_t *e; + msurface_t *s; + glpoly_t *p; + int i,j,k,mark; + float dot; + qboolean rotated; + vec3_t temp, forward, right, up; + + if (!r_drawentities.value) + return; + + for (i=0 ; imodel->type != mod_brush) + continue; + + if (R_CullModelForEntity(e)) + continue; + + if (e->alpha == ENTALPHA_ZERO) + continue; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + rotated = true; + AngleVectors (e->angles, forward, right, up); + VectorCopy (modelorg, temp); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + else + rotated = false; + + s = &e->model->surfaces[e->model->firstmodelsurface]; + + for (j=0 ; jmodel->nummodelsurfaces ; j++, s++) + { + if (s->flags & SURF_DRAWSKY) + { + dot = DotProduct (modelorg, s->plane->normal) - s->plane->dist; + if (((s->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(s->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + //copy the polygon and translate manually, since Sky_ProcessPoly needs it to be in world space + mark = Hunk_LowMark(); + p = (glpoly_t *) Hunk_Alloc (sizeof(*s->polys)); //FIXME: don't allocate for each poly + p->numverts = s->polys->numverts; + for (k=0; knumverts; k++) + { + if (rotated) + { + p->verts[k][0] = e->origin[0] + s->polys->verts[k][0] * forward[0] + - s->polys->verts[k][1] * right[0] + + s->polys->verts[k][2] * up[0]; + p->verts[k][1] = e->origin[1] + s->polys->verts[k][0] * forward[1] + - s->polys->verts[k][1] * right[1] + + s->polys->verts[k][2] * up[1]; + p->verts[k][2] = e->origin[2] + s->polys->verts[k][0] * forward[2] + - s->polys->verts[k][1] * right[2] + + s->polys->verts[k][2] * up[2]; + } + else + VectorAdd(s->polys->verts[k], e->origin, p->verts[k]); + } + Sky_ProcessPoly (p); + Hunk_FreeToLowMark (mark); + } + } + } + } +} + +//============================================================================== +// +// RENDER SKYBOX +// +//============================================================================== + +/* +============== +Sky_EmitSkyBoxVertex +============== +*/ +void Sky_EmitSkyBoxVertex (float s, float t, int axis) +{ + vec3_t v, b; + int j, k; + float w, h; + + b[0] = s * gl_farclip.value / sqrt(3.0); + b[1] = t * gl_farclip.value / sqrt(3.0); + b[2] = gl_farclip.value / sqrt(3.0); + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + v[j] = -b[-k - 1]; + else + v[j] = b[k - 1]; + v[j] += r_origin[j]; + } + + // convert from range [-1,1] to [0,1] + s = (s+1)*0.5; + t = (t+1)*0.5; + + // avoid bilerp seam + w = skybox_textures[skytexorder[axis]]->width; + h = skybox_textures[skytexorder[axis]]->height; + s = s * (w-1)/w + 0.5/w; + t = t * (h-1)/h + 0.5/h; + + t = 1.0 - t; + glTexCoord2f (s, t); + glVertex3fv (v); +} + +/* +============== +Sky_DrawSkyBox + +FIXME: eliminate cracks by adding an extra vert on tjuncs +============== +*/ +void Sky_DrawSkyBox (void) +{ + int i; + + for (i=0 ; i<6 ; i++) + { + if (skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) + continue; + + GL_Bind (skybox_textures[skytexorder[i]]); + +#if 1 //FIXME: this is to avoid tjunctions until i can do it the right way + skymins[0][i] = -1; + skymins[1][i] = -1; + skymaxs[0][i] = 1; + skymaxs[1][i] = 1; +#endif + glBegin (GL_QUADS); + Sky_EmitSkyBoxVertex (skymins[0][i], skymins[1][i], i); + Sky_EmitSkyBoxVertex (skymins[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymins[1][i], i); + glEnd (); + + rs_skypolys++; + rs_skypasses++; + + if (Fog_GetDensity() > 0 && skyfog > 0) + { + float *c; + + c = Fog_GetColor(); + glEnable (GL_BLEND); + glDisable (GL_TEXTURE_2D); + glColor4f (c[0],c[1],c[2], CLAMP(0.0,skyfog,1.0)); + + glBegin (GL_QUADS); + Sky_EmitSkyBoxVertex (skymins[0][i], skymins[1][i], i); + Sky_EmitSkyBoxVertex (skymins[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymins[1][i], i); + glEnd (); + + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + + rs_skypasses++; + } + } +} + +//============================================================================== +// +// RENDER CLOUDS +// +//============================================================================== + +/* +============== +Sky_SetBoxVert +============== +*/ +void Sky_SetBoxVert (float s, float t, int axis, vec3_t v) +{ + vec3_t b; + int j, k; + + b[0] = s * gl_farclip.value / sqrt(3.0); + b[1] = t * gl_farclip.value / sqrt(3.0); + b[2] = gl_farclip.value / sqrt(3.0); + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + v[j] = -b[-k - 1]; + else + v[j] = b[k - 1]; + v[j] += r_origin[j]; + } +} + +/* +============= +Sky_GetTexCoord +============= +*/ +void Sky_GetTexCoord (vec3_t v, float speed, float *s, float *t) +{ + vec3_t dir; + float length, scroll; + + VectorSubtract (v, r_origin, dir); + dir[2] *= 3; // flatten the sphere + + length = dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2]; + length = sqrt (length); + length = 6*63/length; + + scroll = cl.time*speed; + scroll -= (int)scroll & ~127; + + *s = (scroll + dir[0] * length) * (1.0/128); + *t = (scroll + dir[1] * length) * (1.0/128); +} + +/* +=============== +Sky_DrawFaceQuad +=============== +*/ +void Sky_DrawFaceQuad (glpoly_t *p) +{ + float s, t; + float *v; + int i; + + if (gl_mtexable && r_skyalpha.value >= 1.0) + { + GL_Bind (solidskytexture); + GL_EnableMultitexture(); + GL_Bind (alphaskytexture); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 8, &s, &t); + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, s, t); + Sky_GetTexCoord (v, 16, &s, &t); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, s, t); + glVertex3fv (v); + } + glEnd (); + + GL_DisableMultitexture(); + + rs_skypolys++; + rs_skypasses++; + } + else + { + GL_Bind (solidskytexture); + + if (r_skyalpha.value < 1.0) + glColor3f (1, 1, 1); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 8, &s, &t); + glTexCoord2f (s, t); + glVertex3fv (v); + } + glEnd (); + + GL_Bind (alphaskytexture); + glEnable (GL_BLEND); + + if (r_skyalpha.value < 1.0) + glColor4f (1, 1, 1, r_skyalpha.value); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 16, &s, &t); + glTexCoord2f (s, t); + glVertex3fv (v); + } + glEnd (); + + glDisable (GL_BLEND); + + rs_skypolys++; + rs_skypasses += 2; + } + + if (Fog_GetDensity() > 0 && skyfog > 0) + { + float *c; + + c = Fog_GetColor(); + glEnable (GL_BLEND); + glDisable (GL_TEXTURE_2D); + glColor4f (c[0],c[1],c[2], CLAMP(0.0,skyfog,1.0)); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + glVertex3fv (v); + glEnd (); + + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + + rs_skypasses++; + } +} + +/* +============== +Sky_DrawFace +============== +*/ + +void Sky_DrawFace (int axis) +{ + glpoly_t *p; + vec3_t verts[4]; + int i, j, start; + float di,qi,dj,qj; + vec3_t vup, vright, temp, temp2; + + Sky_SetBoxVert(-1.0, -1.0, axis, verts[0]); + Sky_SetBoxVert(-1.0, 1.0, axis, verts[1]); + Sky_SetBoxVert(1.0, 1.0, axis, verts[2]); + Sky_SetBoxVert(1.0, -1.0, axis, verts[3]); + + start = Hunk_LowMark (); + p = (glpoly_t *) Hunk_Alloc(sizeof(glpoly_t)); + + VectorSubtract(verts[2],verts[3],vup); + VectorSubtract(verts[2],verts[1],vright); + + di = q_max((int)r_sky_quality.value, 1); + qi = 1.0 / di; + dj = (axis < 4) ? di*2 : di; //subdivide vertically more than horizontally on skybox sides + qj = 1.0 / dj; + + for (i=0; i skymaxs[0][axis]/2+0.5 || + j*qj < skymins[1][axis]/2+0.5 - qj || j*qj > skymaxs[1][axis]/2+0.5) + continue; + + //if (i&1 ^ j&1) continue; //checkerboard test + VectorScale (vright, qi*i, temp); + VectorScale (vup, qj*j, temp2); + VectorAdd(temp,temp2,temp); + VectorAdd(verts[0],temp,p->verts[0]); + + VectorScale (vup, qj, temp); + VectorAdd (p->verts[0],temp,p->verts[1]); + + VectorScale (vright, qi, temp); + VectorAdd (p->verts[1],temp,p->verts[2]); + + VectorAdd (p->verts[0],temp,p->verts[3]); + + Sky_DrawFaceQuad (p); + } + } + Hunk_FreeToLowMark (start); +} + +/* +============== +Sky_DrawSkyLayers + +draws the old-style scrolling cloud layers +============== +*/ +void Sky_DrawSkyLayers (void) +{ + int i; + + if (r_skyalpha.value < 1.0) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + for (i=0 ; i<6 ; i++) + if (skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i]) + Sky_DrawFace (i); + + if (r_skyalpha.value < 1.0) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +} + +/* +============== +Sky_DrawSky + +called once per frame before drawing anything else +============== +*/ +void Sky_DrawSky (void) +{ + int i; + + //in these special render modes, the sky faces are handled in the normal world/brush renderer + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe ) + return; + + // + // reset sky bounds + // + for (i=0 ; i<6 ; i++) + { + skymins[0][i] = skymins[1][i] = 9999; + skymaxs[0][i] = skymaxs[1][i] = -9999; + } + + // + // process world and bmodels: draw flat-shaded sky surfs, and update skybounds + // + Fog_DisableGFog (); + glDisable (GL_TEXTURE_2D); + if (Fog_GetDensity() > 0) + glColor3fv (Fog_GetColor()); + else + glColor3fv (skyflatcolor); + Sky_ProcessTextureChains (); + Sky_ProcessEntities (); + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + + // + // render slow sky: cloud layers or skybox + // + if (!r_fastsky.value && !(Fog_GetDensity() > 0 && skyfog >= 1)) + { + glDepthFunc(GL_GEQUAL); + glDepthMask(0); + + if (skybox_name[0]) + Sky_DrawSkyBox (); + else + Sky_DrawSkyLayers(); + + glDepthMask(1); + glDepthFunc(GL_LEQUAL); + } + + Fog_EnableGFog (); +} diff --git a/source/gl_texmgr.c b/source/gl_texmgr.c new file mode 100644 index 0000000..188907d --- /dev/null +++ b/source/gl_texmgr.c @@ -0,0 +1,1544 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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. +*/ + +//gl_texmgr.c -- fitzquake's texture manager. manages opengl texture images + +#include "quakedef.h" + +const int gl_solid_format = 3; +const int gl_alpha_format = 4; + +static cvar_t gl_texturemode = {"gl_texturemode", "", CVAR_ARCHIVE}; +static cvar_t gl_texture_anisotropy = {"gl_texture_anisotropy", "1", CVAR_ARCHIVE}; +static cvar_t gl_max_size = {"gl_max_size", "0", CVAR_NONE}; +static cvar_t gl_picmip = {"gl_picmip", "0", CVAR_NONE}; +static GLint gl_hardware_maxsize; + +#define MAX_GLTEXTURES 2048 +static int numgltextures; +static gltexture_t *active_gltextures, *free_gltextures; +gltexture_t *notexture, *nulltexture; + +unsigned int d_8to24table[256]; +unsigned int d_8to24table_fbright[256]; +unsigned int d_8to24table_fbright_fence[256]; +unsigned int d_8to24table_nobright[256]; +unsigned int d_8to24table_nobright_fence[256]; +unsigned int d_8to24table_conchars[256]; +unsigned int d_8to24table_shirt[256]; +unsigned int d_8to24table_pants[256]; + +/* +================================================================================ + + COMMANDS + +================================================================================ +*/ + +typedef struct +{ + int magfilter; + int minfilter; + const char *name; +} glmode_t; +static glmode_t glmodes[] = { + {GL_NEAREST, GL_NEAREST, "GL_NEAREST"}, + {GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, "GL_NEAREST_MIPMAP_NEAREST"}, + {GL_NEAREST, GL_NEAREST_MIPMAP_LINEAR, "GL_NEAREST_MIPMAP_LINEAR"}, + {GL_LINEAR, GL_LINEAR, "GL_LINEAR"}, + {GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, "GL_LINEAR_MIPMAP_NEAREST"}, + {GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, "GL_LINEAR_MIPMAP_LINEAR"}, +}; +#define NUM_GLMODES (int)(sizeof(glmodes)/sizeof(glmodes[0])) +static int glmode_idx = NUM_GLMODES - 1; /* trilinear */ + +/* +=============== +TexMgr_DescribeTextureModes_f -- report available texturemodes +=============== +*/ +static void TexMgr_DescribeTextureModes_f (void) +{ + int i; + + for (i = 0; i < NUM_GLMODES; i++) + Con_SafePrintf (" %2i: %s\n", i + 1, glmodes[i].name); + + Con_Printf ("%i modes\n", i); +} + +/* +=============== +TexMgr_SetFilterModes +=============== +*/ +static void TexMgr_SetFilterModes (gltexture_t *glt) +{ + GL_Bind (glt); + + if (glt->flags & TEXPREF_NEAREST) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else if (glt->flags & TEXPREF_LINEAR) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else if (glt->flags & TEXPREF_MIPMAP) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].minfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value); + } + else + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].magfilter); + } +} + +/* +=============== +TexMgr_TextureMode_f -- called when gl_texturemode changes +=============== +*/ +static void TexMgr_TextureMode_f (cvar_t *var) +{ + gltexture_t *glt; + int i; + + for (i = 0; i < NUM_GLMODES; i++) + { + if (!Q_strcmp (glmodes[i].name, gl_texturemode.string)) + { + if (glmode_idx != i) + { + glmode_idx = i; + for (glt = active_gltextures; glt; glt = glt->next) + TexMgr_SetFilterModes (glt); + Sbar_Changed (); //sbar graphics need to be redrawn with new filter mode + //FIXME: warpimages need to be redrawn, too. + } + return; + } + } + + for (i = 0; i < NUM_GLMODES; i++) + { + if (!q_strcasecmp (glmodes[i].name, gl_texturemode.string)) + { + Cvar_SetQuick (&gl_texturemode, glmodes[i].name); + return; + } + } + + i = atoi(gl_texturemode.string); + if (i >= 1 && i <= NUM_GLMODES) + { + Cvar_SetQuick (&gl_texturemode, glmodes[i-1].name); + return; + } + + Con_Printf ("\"%s\" is not a valid texturemode\n", gl_texturemode.string); + Cvar_SetQuick (&gl_texturemode, glmodes[glmode_idx].name); +} + +/* +=============== +TexMgr_Anisotropy_f -- called when gl_texture_anisotropy changes +=============== +*/ +static void TexMgr_Anisotropy_f (cvar_t *var) +{ + if (gl_texture_anisotropy.value < 1) + { + Cvar_SetQuick (&gl_texture_anisotropy, "1"); + } + else if (gl_texture_anisotropy.value > gl_max_anisotropy) + { + Cvar_SetValueQuick (&gl_texture_anisotropy, gl_max_anisotropy); + } + else + { + gltexture_t *glt; + for (glt = active_gltextures; glt; glt = glt->next) + { + /* TexMgr_SetFilterModes (glt);*/ + if (glt->flags & TEXPREF_MIPMAP) { + GL_Bind (glt); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].minfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value); + } + } + } +} + +/* +=============== +TexMgr_Imagelist_f -- report loaded textures +=============== +*/ +static void TexMgr_Imagelist_f (void) +{ + float mb; + float texels = 0; + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + Con_SafePrintf (" %4i x%4i %s\n", glt->width, glt->height, glt->name); + if (glt->flags & TEXPREF_MIPMAP) + texels += glt->width * glt->height * 4.0f / 3.0f; + else + texels += (glt->width * glt->height); + } + + mb = texels * (Cvar_VariableValue("vid_bpp") / 8.0f) / 0x100000; + Con_Printf ("%i textures %i pixels %1.1f megabytes\n", numgltextures, (int)texels, mb); +} + +/* +=============== +TexMgr_Imagedump_f -- dump all current textures to TGA files +=============== +*/ +static void TexMgr_Imagedump_f (void) +{ + char tganame[MAX_OSPATH], tempname[MAX_OSPATH], dirname[MAX_OSPATH]; + gltexture_t *glt; + byte *buffer; + char *c; + + //create directory + q_snprintf(dirname, sizeof(dirname), "%s/imagedump", com_gamedir); + Sys_mkdir (dirname); + + //loop through textures + for (glt = active_gltextures; glt; glt = glt->next) + { + q_strlcpy (tempname, glt->name, sizeof(tempname)); + while ( (c = strchr(tempname, ':')) ) *c = '_'; + while ( (c = strchr(tempname, '/')) ) *c = '_'; + while ( (c = strchr(tempname, '*')) ) *c = '_'; + q_snprintf(tganame, sizeof(tganame), "imagedump/%s.tga", tempname); + + GL_Bind (glt); + glPixelStorei (GL_PACK_ALIGNMENT, 1);/* for widths that aren't a multiple of 4 */ + + if (glt->flags & TEXPREF_ALPHA) + { + buffer = (byte *) malloc(glt->width*glt->height*4); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + Image_WriteTGA (tganame, buffer, glt->width, glt->height, 32, true); + } + else + { + buffer = (byte *) malloc(glt->width*glt->height*3); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer); + Image_WriteTGA (tganame, buffer, glt->width, glt->height, 24, true); + } + free (buffer); + } + + Con_Printf ("dumped %i textures to %s\n", numgltextures, dirname); +} + +/* +=============== +TexMgr_FrameUsage -- report texture memory usage for this frame +=============== +*/ +float TexMgr_FrameUsage (void) +{ + float mb; + float texels = 0; + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->visframe == r_framecount) + { + if (glt->flags & TEXPREF_MIPMAP) + texels += glt->width * glt->height * 4.0f / 3.0f; + else + texels += (glt->width * glt->height); + } + } + + mb = texels * (Cvar_VariableValue("vid_bpp") / 8.0f) / 0x100000; + return mb; +} + +/* +================================================================================ + + TEXTURE MANAGER + +================================================================================ +*/ + +/* +================ +TexMgr_FindTexture +================ +*/ +gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name) +{ + gltexture_t *glt; + + if (name) + { + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->owner == owner && !strcmp (glt->name, name)) + return glt; + } + } + + return NULL; +} + +/* +================ +TexMgr_NewTexture +================ +*/ +gltexture_t *TexMgr_NewTexture (void) +{ + gltexture_t *glt; + + if (numgltextures == MAX_GLTEXTURES) + Sys_Error("numgltextures == MAX_GLTEXTURES\n"); + + glt = free_gltextures; + free_gltextures = glt->next; + glt->next = active_gltextures; + active_gltextures = glt; + + glGenTextures(1, &glt->texnum); + numgltextures++; + return glt; +} + +static void GL_DeleteTexture (gltexture_t *texture); + +//ericw -- workaround for preventing TexMgr_FreeTexture during TexMgr_ReloadImages +static qboolean in_reload_images; + +/* +================ +TexMgr_FreeTexture +================ +*/ +void TexMgr_FreeTexture (gltexture_t *kill) +{ + gltexture_t *glt; + + if (in_reload_images) + return; + + if (kill == NULL) + { + Con_Printf ("TexMgr_FreeTexture: NULL texture\n"); + return; + } + + if (active_gltextures == kill) + { + active_gltextures = kill->next; + kill->next = free_gltextures; + free_gltextures = kill; + + GL_DeleteTexture(kill); + numgltextures--; + return; + } + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->next == kill) + { + glt->next = kill->next; + kill->next = free_gltextures; + free_gltextures = kill; + + GL_DeleteTexture(kill); + numgltextures--; + return; + } + } + + Con_Printf ("TexMgr_FreeTexture: not found\n"); +} + +/* +================ +TexMgr_FreeTextures + +compares each bit in "flags" to the one in glt->flags only if that bit is active in "mask" +================ +*/ +void TexMgr_FreeTextures (unsigned int flags, unsigned int mask) +{ + gltexture_t *glt, *next; + + for (glt = active_gltextures; glt; glt = next) + { + next = glt->next; + if ((glt->flags & mask) == (flags & mask)) + TexMgr_FreeTexture (glt); + } +} + +/* +================ +TexMgr_FreeTexturesForOwner +================ +*/ +void TexMgr_FreeTexturesForOwner (qmodel_t *owner) +{ + gltexture_t *glt, *next; + + for (glt = active_gltextures; glt; glt = next) + { + next = glt->next; + if (glt && glt->owner == owner) + TexMgr_FreeTexture (glt); + } +} + +/* +================ +TexMgr_DeleteTextureObjects +================ +*/ +void TexMgr_DeleteTextureObjects (void) +{ + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + GL_DeleteTexture (glt); + } +} + +/* +================================================================================ + + INIT + +================================================================================ +*/ + +/* +================= +TexMgr_LoadPalette -- johnfitz -- was VID_SetPalette, moved here, renamed, rewritten +================= +*/ +void TexMgr_LoadPalette (void) +{ + byte *pal, *src, *dst; + int i, mark; + FILE *f; + + COM_FOpenFile ("gfx/palette.lmp", &f, NULL); + if (!f) + Sys_Error ("Couldn't load gfx/palette.lmp"); + + mark = Hunk_LowMark (); + pal = (byte *) Hunk_Alloc (768); + fread (pal, 1, 768, f); + fclose(f); + + //standard palette, 255 is transparent + dst = (byte *)d_8to24table; + src = pal; + for (i = 0; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + ((byte *) &d_8to24table[255]) [3] = 0; + + //fullbright palette, 0-223 are black (for additive blending) + src = pal + 224*3; + dst = (byte *) &d_8to24table_fbright[224]; + for (i = 224; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + for (i = 0; i < 224; i++) + { + dst = (byte *) &d_8to24table_fbright[i]; + dst[3] = 255; + dst[2] = dst[1] = dst[0] = 0; + } + + //nobright palette, 224-255 are black (for additive blending) + dst = (byte *)d_8to24table_nobright; + src = pal; + for (i = 0; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + for (i = 224; i < 256; i++) + { + dst = (byte *) &d_8to24table_nobright[i]; + dst[3] = 255; + dst[2] = dst[1] = dst[0] = 0; + } + + //fullbright palette, for fence textures + memcpy(d_8to24table_fbright_fence, d_8to24table_fbright, 256*4); + d_8to24table_fbright_fence[255] = 0; // Alpha of zero. + + //nobright palette, for fence textures + memcpy(d_8to24table_nobright_fence, d_8to24table_nobright, 256*4); + d_8to24table_nobright_fence[255] = 0; // Alpha of zero. + + //conchars palette, 0 and 255 are transparent + memcpy(d_8to24table_conchars, d_8to24table, 256*4); + ((byte *) &d_8to24table_conchars[0]) [3] = 0; + + Hunk_FreeToLowMark (mark); +} + +/* +================ +TexMgr_NewGame +================ +*/ +void TexMgr_NewGame (void) +{ + TexMgr_FreeTextures (0, TEXPREF_PERSIST); //deletes all textures where TEXPREF_PERSIST is unset + TexMgr_LoadPalette (); +} + +/* +============= +TexMgr_RecalcWarpImageSize -- called during init, and after a vid_restart + +choose safe warpimage size and resize existing warpimage textures +============= +*/ +void TexMgr_RecalcWarpImageSize (void) +{ +// int oldsize = gl_warpimagesize; + int mark; + gltexture_t *glt; + byte *dummy; + + // + // find the new correct size + // + gl_warpimagesize = TexMgr_SafeTextureSize (512); + + while (gl_warpimagesize > vid.width) + gl_warpimagesize >>= 1; + while (gl_warpimagesize > vid.height) + gl_warpimagesize >>= 1; + + // ericw -- removed early exit if (gl_warpimagesize == oldsize). + // after vid_restart TexMgr_ReloadImage reloads textures + // to tx->source_width/source_height, which might not match oldsize. + // fixes: https://sourceforge.net/p/quakespasm/bugs/13/ + + // + // resize the textures in opengl + // + mark = Hunk_LowMark(); + dummy = (byte *) Hunk_Alloc (gl_warpimagesize*gl_warpimagesize*4); + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->flags & TEXPREF_WARPIMAGE) + { + GL_Bind (glt); + glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, gl_warpimagesize, gl_warpimagesize, 0, GL_RGBA, GL_UNSIGNED_BYTE, dummy); + glt->width = glt->height = gl_warpimagesize; + } + } + + Hunk_FreeToLowMark (mark); +} + +/* +================ +TexMgr_Init + +must be called before any texture loading +================ +*/ +void TexMgr_Init (void) +{ + int i; + static byte notexture_data[16] = {159,91,83,255,0,0,0,255,0,0,0,255,159,91,83,255}; //black and pink checker + static byte nulltexture_data[16] = {127,191,255,255,0,0,0,255,0,0,0,255,127,191,255,255}; //black and blue checker + extern texture_t *r_notexture_mip, *r_notexture_mip2; + + // init texture list + free_gltextures = (gltexture_t *) Hunk_AllocName (MAX_GLTEXTURES * sizeof(gltexture_t), "gltextures"); + active_gltextures = NULL; + for (i = 0; i < MAX_GLTEXTURES - 1; i++) + free_gltextures[i].next = &free_gltextures[i+1]; + free_gltextures[i].next = NULL; + numgltextures = 0; + + // palette + TexMgr_LoadPalette (); + + Cvar_RegisterVariable (&gl_max_size); + Cvar_RegisterVariable (&gl_picmip); + Cvar_RegisterVariable (&gl_texture_anisotropy); + Cvar_SetCallback (&gl_texture_anisotropy, &TexMgr_Anisotropy_f); + gl_texturemode.string = glmodes[glmode_idx].name; + Cvar_RegisterVariable (&gl_texturemode); + Cvar_SetCallback (&gl_texturemode, &TexMgr_TextureMode_f); + Cmd_AddCommand ("gl_describetexturemodes", &TexMgr_DescribeTextureModes_f); + Cmd_AddCommand ("imagelist", &TexMgr_Imagelist_f); + Cmd_AddCommand ("imagedump", &TexMgr_Imagedump_f); + + // poll max size from hardware + glGetIntegerv (GL_MAX_TEXTURE_SIZE, &gl_hardware_maxsize); + + // load notexture images + notexture = TexMgr_LoadImage (NULL, "notexture", 2, 2, SRC_RGBA, notexture_data, "", (src_offset_t)notexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP); + nulltexture = TexMgr_LoadImage (NULL, "nulltexture", 2, 2, SRC_RGBA, nulltexture_data, "", (src_offset_t)nulltexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP); + + //have to assign these here becuase Mod_Init is called before TexMgr_Init + r_notexture_mip->gltexture = r_notexture_mip2->gltexture = notexture; + + //set safe size for warpimages + gl_warpimagesize = 0; + TexMgr_RecalcWarpImageSize (); +} + +/* +================================================================================ + + IMAGE LOADING + +================================================================================ +*/ + +/* +================ +TexMgr_Pad -- return smallest power of two greater than or equal to s +================ +*/ +int TexMgr_Pad (int s) +{ + int i; + for (i = 1; i < s; i<<=1) + ; + return i; +} + +/* +=============== +TexMgr_SafeTextureSize -- return a size with hardware and user prefs in mind +=============== +*/ +int TexMgr_SafeTextureSize (int s) +{ + if (!gl_texture_NPOT) + s = TexMgr_Pad(s); + if ((int)gl_max_size.value > 0) + s = q_min(TexMgr_Pad((int)gl_max_size.value), s); + s = q_min(gl_hardware_maxsize, s); + return s; +} + +/* +================ +TexMgr_PadConditional -- only pad if a texture of that size would be padded. (used for tex coords) +================ +*/ +int TexMgr_PadConditional (int s) +{ + if (s < TexMgr_SafeTextureSize(s)) + return TexMgr_Pad(s); + else + return s; +} + +/* +================ +TexMgr_MipMapW +================ +*/ +static unsigned *TexMgr_MipMapW (unsigned *data, int width, int height) +{ + int i, size; + byte *out, *in; + + out = in = (byte *)data; + size = (width*height)>>1; + + for (i = 0; i < size; i++, out += 4, in += 8) + { + out[0] = (in[0] + in[4])>>1; + out[1] = (in[1] + in[5])>>1; + out[2] = (in[2] + in[6])>>1; + out[3] = (in[3] + in[7])>>1; + } + + return data; +} + +/* +================ +TexMgr_MipMapH +================ +*/ +static unsigned *TexMgr_MipMapH (unsigned *data, int width, int height) +{ + int i, j; + byte *out, *in; + + out = in = (byte *)data; + height>>=1; + width<<=2; + + for (i = 0; i < height; i++, in += width) + { + for (j = 0; j < width; j += 4, out += 4, in += 4) + { + out[0] = (in[0] + in[width+0])>>1; + out[1] = (in[1] + in[width+1])>>1; + out[2] = (in[2] + in[width+2])>>1; + out[3] = (in[3] + in[width+3])>>1; + } + } + + return data; +} + +/* +================ +TexMgr_ResampleTexture -- bilinear resample +================ +*/ +static unsigned *TexMgr_ResampleTexture (unsigned *in, int inwidth, int inheight, qboolean alpha) +{ + byte *nwpx, *nepx, *swpx, *sepx, *dest; + unsigned xfrac, yfrac, x, y, modx, mody, imodx, imody, injump, outjump; + unsigned *out; + int i, j, outwidth, outheight; + + if (inwidth == TexMgr_Pad(inwidth) && inheight == TexMgr_Pad(inheight)) + return in; + + outwidth = TexMgr_Pad(inwidth); + outheight = TexMgr_Pad(inheight); + out = (unsigned *) Hunk_Alloc(outwidth*outheight*4); + + xfrac = ((inwidth-1) << 16) / (outwidth-1); + yfrac = ((inheight-1) << 16) / (outheight-1); + y = outjump = 0; + + for (i = 0; i < outheight; i++) + { + mody = (y>>8) & 0xFF; + imody = 256 - mody; + injump = (y>>16) * inwidth; + x = 0; + + for (j = 0; j < outwidth; j++) + { + modx = (x>>8) & 0xFF; + imodx = 256 - modx; + + nwpx = (byte *)(in + (x>>16) + injump); + nepx = nwpx + 4; + swpx = nwpx + inwidth*4; + sepx = swpx + 4; + + dest = (byte *)(out + outjump + j); + + dest[0] = (nwpx[0]*imodx*imody + nepx[0]*modx*imody + swpx[0]*imodx*mody + sepx[0]*modx*mody)>>16; + dest[1] = (nwpx[1]*imodx*imody + nepx[1]*modx*imody + swpx[1]*imodx*mody + sepx[1]*modx*mody)>>16; + dest[2] = (nwpx[2]*imodx*imody + nepx[2]*modx*imody + swpx[2]*imodx*mody + sepx[2]*modx*mody)>>16; + if (alpha) + dest[3] = (nwpx[3]*imodx*imody + nepx[3]*modx*imody + swpx[3]*imodx*mody + sepx[3]*modx*mody)>>16; + else + dest[3] = 255; + + x += xfrac; + } + outjump += outwidth; + y += yfrac; + } + + return out; +} + +/* +=============== +TexMgr_AlphaEdgeFix + +eliminate pink edges on sprites, etc. +operates in place on 32bit data +=============== +*/ +static void TexMgr_AlphaEdgeFix (byte *data, int width, int height) +{ + int i, j, n = 0, b, c[3] = {0,0,0}, + lastrow, thisrow, nextrow, + lastpix, thispix, nextpix; + byte *dest = data; + + for (i = 0; i < height; i++) + { + lastrow = width * 4 * ((i == 0) ? height-1 : i-1); + thisrow = width * 4 * i; + nextrow = width * 4 * ((i == height-1) ? 0 : i+1); + + for (j = 0; j < width; j++, dest += 4) + { + if (dest[3]) //not transparent + continue; + + lastpix = 4 * ((j == 0) ? width-1 : j-1); + thispix = 4 * j; + nextpix = 4 * ((j == width-1) ? 0 : j+1); + + b = lastrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = thisrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = lastrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = lastrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = thisrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + + //average all non-transparent neighbors + if (n) + { + dest[0] = (byte)(c[0]/n); + dest[1] = (byte)(c[1]/n); + dest[2] = (byte)(c[2]/n); + + n = c[0] = c[1] = c[2] = 0; + } + } + } +} + +/* +=============== +TexMgr_PadEdgeFixW -- special case of AlphaEdgeFix for textures that only need it because they were padded + +operates in place on 32bit data, and expects unpadded height and width values +=============== +*/ +static void TexMgr_PadEdgeFixW (byte *data, int width, int height) +{ + byte *src, *dst; + int i, padw, padh; + + padw = TexMgr_PadConditional(width); + padh = TexMgr_PadConditional(height); + + //copy last full column to first empty column, leaving alpha byte at zero + src = data + (width - 1) * 4; + for (i = 0; i < padh; i++) + { + src[4] = src[0]; + src[5] = src[1]; + src[6] = src[2]; + src += padw * 4; + } + + //copy first full column to last empty column, leaving alpha byte at zero + src = data; + dst = data + (padw - 1) * 4; + for (i = 0; i < padh; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += padw * 4; + dst += padw * 4; + } +} + +/* +=============== +TexMgr_PadEdgeFixH -- special case of AlphaEdgeFix for textures that only need it because they were padded + +operates in place on 32bit data, and expects unpadded height and width values +=============== +*/ +static void TexMgr_PadEdgeFixH (byte *data, int width, int height) +{ + byte *src, *dst; + int i, padw, padh; + + padw = TexMgr_PadConditional(width); + padh = TexMgr_PadConditional(height); + + //copy last full row to first empty row, leaving alpha byte at zero + dst = data + height * padw * 4; + src = dst - padw * 4; + for (i = 0; i < padw; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += 4; + dst += 4; + } + + //copy first full row to last empty row, leaving alpha byte at zero + dst = data + (padh - 1) * padw * 4; + src = data; + for (i = 0; i < padw; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += 4; + dst += 4; + } +} + +/* +================ +TexMgr_8to32 +================ +*/ +static unsigned *TexMgr_8to32 (byte *in, int pixels, unsigned int *usepal) +{ + int i; + unsigned *out, *data; + + out = data = (unsigned *) Hunk_Alloc(pixels*4); + + for (i = 0; i < pixels; i++) + *out++ = usepal[*in++]; + + return data; +} + +/* +================ +TexMgr_PadImageW -- return image with width padded up to power-of-two dimentions +================ +*/ +static byte *TexMgr_PadImageW (byte *in, int width, int height, byte padbyte) +{ + int i, j, outwidth; + byte *out, *data; + + if (width == TexMgr_Pad(width)) + return in; + + outwidth = TexMgr_Pad(width); + + out = data = (byte *) Hunk_Alloc(outwidth*height); + + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + *out++ = *in++; + for ( ; j < outwidth; j++) + *out++ = padbyte; + } + + return data; +} + +/* +================ +TexMgr_PadImageH -- return image with height padded up to power-of-two dimentions +================ +*/ +static byte *TexMgr_PadImageH (byte *in, int width, int height, byte padbyte) +{ + int i, srcpix, dstpix; + byte *data, *out; + + if (height == TexMgr_Pad(height)) + return in; + + srcpix = width * height; + dstpix = width * TexMgr_Pad(height); + + out = data = (byte *) Hunk_Alloc(dstpix); + + for (i = 0; i < srcpix; i++) + *out++ = *in++; + for ( ; i < dstpix; i++) + *out++ = padbyte; + + return data; +} + +/* +================ +TexMgr_LoadImage32 -- handles 32bit source data +================ +*/ +static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data) +{ + int internalformat, miplevel, mipwidth, mipheight, picmip; + + if (!gl_texture_NPOT) + { + // resample up + data = TexMgr_ResampleTexture (data, glt->width, glt->height, glt->flags & TEXPREF_ALPHA); + glt->width = TexMgr_Pad(glt->width); + glt->height = TexMgr_Pad(glt->height); + } + + // mipmap down + picmip = (glt->flags & TEXPREF_NOPICMIP) ? 0 : q_max((int)gl_picmip.value, 0); + mipwidth = TexMgr_SafeTextureSize (glt->width >> picmip); + mipheight = TexMgr_SafeTextureSize (glt->height >> picmip); + while ((int) glt->width > mipwidth) + { + TexMgr_MipMapW (data, glt->width, glt->height); + glt->width >>= 1; + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height); + } + while ((int) glt->height > mipheight) + { + TexMgr_MipMapH (data, glt->width, glt->height); + glt->height >>= 1; + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height); + } + + // upload + GL_Bind (glt); + internalformat = (glt->flags & TEXPREF_ALPHA) ? gl_alpha_format : gl_solid_format; + glTexImage2D (GL_TEXTURE_2D, 0, internalformat, glt->width, glt->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + // upload mipmaps + if (glt->flags & TEXPREF_MIPMAP) + { + mipwidth = glt->width; + mipheight = glt->height; + + for (miplevel=1; mipwidth > 1 || mipheight > 1; miplevel++) + { + if (mipwidth > 1) + { + TexMgr_MipMapW (data, mipwidth, mipheight); + mipwidth >>= 1; + } + if (mipheight > 1) + { + TexMgr_MipMapH (data, mipwidth, mipheight); + mipheight >>= 1; + } + glTexImage2D (GL_TEXTURE_2D, miplevel, internalformat, mipwidth, mipheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + } + + // set filter modes + TexMgr_SetFilterModes (glt); +} + +/* +================ +TexMgr_LoadImage8 -- handles 8bit source data, then passes it to LoadImage32 +================ +*/ +static void TexMgr_LoadImage8 (gltexture_t *glt, byte *data) +{ + extern cvar_t gl_fullbrights; + qboolean padw = false, padh = false; + byte padbyte; + unsigned int *usepal; + int i; + + // HACK HACK HACK -- taken from tomazquake + if (strstr(glt->name, "shot1sid") && + glt->width == 32 && glt->height == 32 && + CRC_Block(data, 1024) == 65393) + { + // This texture in b_shell1.bsp has some of the first 32 pixels painted white. + // They are invisible in software, but look really ugly in GL. So we just copy + // 32 pixels from the bottom to make it look nice. + memcpy (data, data + 32*31, 32); + } + + // detect false alpha cases + if (glt->flags & TEXPREF_ALPHA && !(glt->flags & TEXPREF_CONCHARS)) + { + for (i = 0; i < (int) (glt->width * glt->height); i++) + if (data[i] == 255) //transparent index + break; + if (i == (int) (glt->width * glt->height)) + glt->flags -= TEXPREF_ALPHA; + } + + // choose palette and padbyte + if (glt->flags & TEXPREF_FULLBRIGHT) + { + if (glt->flags & TEXPREF_ALPHA) + usepal = d_8to24table_fbright_fence; + else + usepal = d_8to24table_fbright; + padbyte = 0; + } + else if (glt->flags & TEXPREF_NOBRIGHT && gl_fullbrights.value) + { + if (glt->flags & TEXPREF_ALPHA) + usepal = d_8to24table_nobright_fence; + else + usepal = d_8to24table_nobright; + padbyte = 0; + } + else if (glt->flags & TEXPREF_CONCHARS) + { + usepal = d_8to24table_conchars; + padbyte = 0; + } + else + { + usepal = d_8to24table; + padbyte = 255; + } + + // pad each dimention, but only if it's not going to be downsampled later + if (glt->flags & TEXPREF_PAD) + { + if ((int) glt->width < TexMgr_SafeTextureSize(glt->width)) + { + data = TexMgr_PadImageW (data, glt->width, glt->height, padbyte); + glt->width = TexMgr_Pad(glt->width); + padw = true; + } + if ((int) glt->height < TexMgr_SafeTextureSize(glt->height)) + { + data = TexMgr_PadImageH (data, glt->width, glt->height, padbyte); + glt->height = TexMgr_Pad(glt->height); + padh = true; + } + } + + // convert to 32bit + data = (byte *)TexMgr_8to32(data, glt->width * glt->height, usepal); + + // fix edges + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix (data, glt->width, glt->height); + else + { + if (padw) + TexMgr_PadEdgeFixW (data, glt->source_width, glt->source_height); + if (padh) + TexMgr_PadEdgeFixH (data, glt->source_width, glt->source_height); + } + + // upload it + TexMgr_LoadImage32 (glt, (unsigned *)data); +} + +/* +================ +TexMgr_LoadLightmap -- handles lightmap data +================ +*/ +static void TexMgr_LoadLightmap (gltexture_t *glt, byte *data) +{ + // upload it + GL_Bind (glt); + glTexImage2D (GL_TEXTURE_2D, 0, lightmap_bytes, glt->width, glt->height, 0, gl_lightmap_format, GL_UNSIGNED_BYTE, data); + + // set filter modes + TexMgr_SetFilterModes (glt); +} + +/* +================ +TexMgr_LoadImage -- the one entry point for loading all textures +================ +*/ +gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int height, enum srcformat format, + byte *data, const char *source_file, src_offset_t source_offset, unsigned flags) +{ + unsigned short crc; + gltexture_t *glt; + int mark; + + if (isDedicated) + return NULL; + + // cache check + switch (format) + { + case SRC_INDEXED: + crc = CRC_Block(data, width * height); + break; + case SRC_LIGHTMAP: + crc = CRC_Block(data, width * height * lightmap_bytes); + break; + case SRC_RGBA: + crc = CRC_Block(data, width * height * 4); + break; + default: /* not reachable but avoids compiler warnings */ + crc = 0; + } + if ((flags & TEXPREF_OVERWRITE) && (glt = TexMgr_FindTexture (owner, name))) + { + if (glt->source_crc == crc) + return glt; + } + else + glt = TexMgr_NewTexture (); + + // copy data + glt->owner = owner; + q_strlcpy (glt->name, name, sizeof(glt->name)); + glt->width = width; + glt->height = height; + glt->flags = flags; + glt->shirt = -1; + glt->pants = -1; + q_strlcpy (glt->source_file, source_file, sizeof(glt->source_file)); + glt->source_offset = source_offset; + glt->source_format = format; + glt->source_width = width; + glt->source_height = height; + glt->source_crc = crc; + + //upload it + mark = Hunk_LowMark(); + + switch (glt->source_format) + { + case SRC_INDEXED: + TexMgr_LoadImage8 (glt, data); + break; + case SRC_LIGHTMAP: + TexMgr_LoadLightmap (glt, data); + break; + case SRC_RGBA: + TexMgr_LoadImage32 (glt, (unsigned *)data); + break; + } + + Hunk_FreeToLowMark(mark); + + return glt; +} + +/* +================================================================================ + + COLORMAPPING AND TEXTURE RELOADING + +================================================================================ +*/ + +/* +================ +TexMgr_ReloadImage -- reloads a texture, and colormaps it if needed +================ +*/ +void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants) +{ + byte translation[256]; + byte *src, *dst, *data = NULL, *translated; + int mark, size, i; +// +// get source data +// + mark = Hunk_LowMark (); + + if (glt->source_file[0] && glt->source_offset) + { + //lump inside file + long size; + FILE *f; + COM_FOpenFile(glt->source_file, &f, NULL); + if (!f) + goto invalid; + fseek (f, glt->source_offset, SEEK_CUR); + size = (long) (glt->source_width * glt->source_height); + /* should be SRC_INDEXED, but no harm being paranoid: */ + if (glt->source_format == SRC_RGBA) + size *= 4; + else if (glt->source_format == SRC_LIGHTMAP) + size *= lightmap_bytes; + data = (byte *) Hunk_Alloc (size); + fread (data, 1, size, f); + fclose (f); + } + else if (glt->source_file[0] && !glt->source_offset) + data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height); //simple file + else if (!glt->source_file[0] && glt->source_offset) + data = (byte *) glt->source_offset; //image in memory + + if (!data) + { +invalid: + Con_Printf ("TexMgr_ReloadImage: invalid source for %s\n", glt->name); + Hunk_FreeToLowMark(mark); + return; + } + + glt->width = glt->source_width; + glt->height = glt->source_height; +// +// apply shirt and pants colors +// +// if shirt and pants are -1,-1, use existing shirt and pants colors +// if existing shirt and pants colors are -1,-1, don't bother colormapping + if (shirt > -1 && pants > -1) + { + if (glt->source_format == SRC_INDEXED) + { + glt->shirt = shirt; + glt->pants = pants; + } + else + Con_Printf ("TexMgr_ReloadImage: can't colormap a non SRC_INDEXED texture: %s\n", glt->name); + } + if (glt->shirt > -1 && glt->pants > -1) + { + //create new translation table + for (i = 0; i < 256; i++) + translation[i] = i; + + shirt = glt->shirt * 16; + if (shirt < 128) + { + for (i = 0; i < 16; i++) + translation[TOP_RANGE+i] = shirt + i; + } + else + { + for (i = 0; i < 16; i++) + translation[TOP_RANGE+i] = shirt+15-i; + } + + pants = glt->pants * 16; + if (pants < 128) + { + for (i = 0; i < 16; i++) + translation[BOTTOM_RANGE+i] = pants + i; + } + else + { + for (i = 0; i < 16; i++) + translation[BOTTOM_RANGE+i] = pants+15-i; + } + + //translate texture + size = glt->width * glt->height; + dst = translated = (byte *) Hunk_Alloc (size); + src = data; + + for (i = 0; i < size; i++) + *dst++ = translation[*src++]; + + data = translated; + } +// +// upload it +// + switch (glt->source_format) + { + case SRC_INDEXED: + TexMgr_LoadImage8 (glt, data); + break; + case SRC_LIGHTMAP: + TexMgr_LoadLightmap (glt, data); + break; + case SRC_RGBA: + TexMgr_LoadImage32 (glt, (unsigned *)data); + break; + } + + Hunk_FreeToLowMark(mark); +} + +/* +================ +TexMgr_ReloadImages -- reloads all texture images. called only by vid_restart +================ +*/ +void TexMgr_ReloadImages (void) +{ + gltexture_t *glt; + +// ericw -- tricky bug: if the hunk is almost full, an allocation in TexMgr_ReloadImage +// triggers cache items to be freed, which calls back into TexMgr to free the +// texture. If this frees 'glt' in the loop below, the active_gltextures +// list gets corrupted. +// A test case is jam3_tronyn.bsp with -heapsize 65536, and do several mode +// switches/fullscreen toggles +// 2015-09-04 -- Cache_Flush workaround was causing issues (http://sourceforge.net/p/quakespasm/bugs/10/) +// switching to a boolean flag. + in_reload_images = true; + + for (glt = active_gltextures; glt; glt = glt->next) + { + glGenTextures(1, &glt->texnum); + TexMgr_ReloadImage (glt, -1, -1); + } + + in_reload_images = false; +} + +/* +================ +TexMgr_ReloadNobrightImages -- reloads all texture that were loaded with the nobright palette. called when gl_fullbrights changes +================ +*/ +void TexMgr_ReloadNobrightImages (void) +{ + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + if (glt->flags & TEXPREF_NOBRIGHT) + TexMgr_ReloadImage(glt, -1, -1); +} + +/* +================================================================================ + + TEXTURE BINDING / TEXTURE UNIT SWITCHING + +================================================================================ +*/ + +static GLuint currenttexture[3] = {GL_UNUSED_TEXTURE, GL_UNUSED_TEXTURE, GL_UNUSED_TEXTURE}; // to avoid unnecessary texture sets +static GLenum currenttarget = GL_TEXTURE0_ARB; +qboolean mtexenabled = false; + +/* +================ +GL_SelectTexture -- johnfitz -- rewritten +================ +*/ +void GL_SelectTexture (GLenum target) +{ + if (target == currenttarget) + return; + + GL_SelectTextureFunc(target); + currenttarget = target; +} + +/* +================ +GL_DisableMultitexture -- selects texture unit 0 +================ +*/ +void GL_DisableMultitexture(void) +{ + if (mtexenabled) + { + glDisable(GL_TEXTURE_2D); + GL_SelectTexture(GL_TEXTURE0_ARB); + mtexenabled = false; + } +} + +/* +================ +GL_EnableMultitexture -- selects texture unit 1 +================ +*/ +void GL_EnableMultitexture(void) +{ + if (gl_mtexable) + { + GL_SelectTexture(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + mtexenabled = true; + } +} + +/* +================ +GL_Bind -- johnfitz -- heavy revision +================ +*/ +void GL_Bind (gltexture_t *texture) +{ + if (!texture) + texture = nulltexture; + + if (texture->texnum != currenttexture[currenttarget - GL_TEXTURE0_ARB]) + { + currenttexture[currenttarget - GL_TEXTURE0_ARB] = texture->texnum; + glBindTexture (GL_TEXTURE_2D, texture->texnum); + texture->visframe = r_framecount; + } +} + +/* +================ +GL_DeleteTexture -- ericw + +Wrapper around glDeleteTextures that also clears the given texture number +from our per-TMU cached texture binding table. +================ +*/ +static void GL_DeleteTexture (gltexture_t *texture) +{ + glDeleteTextures (1, &texture->texnum); + + if (texture->texnum == currenttexture[0]) currenttexture[0] = GL_UNUSED_TEXTURE; + if (texture->texnum == currenttexture[1]) currenttexture[1] = GL_UNUSED_TEXTURE; + if (texture->texnum == currenttexture[2]) currenttexture[2] = GL_UNUSED_TEXTURE; + + texture->texnum = 0; +} + +/* +================ +GL_ClearBindings -- ericw + +Invalidates cached bindings, so the next GL_Bind calls for each TMU will +make real glBindTexture calls. +Call this after changing the binding outside of GL_Bind. +================ +*/ +void GL_ClearBindings(void) +{ + int i; + for (i = 0; i < 3; i++) + { + currenttexture[i] = GL_UNUSED_TEXTURE; + } +} diff --git a/source/gl_texmgr.h b/source/gl_texmgr.h new file mode 100644 index 0000000..6597b0c --- /dev/null +++ b/source/gl_texmgr.h @@ -0,0 +1,110 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _GL_TEXMAN_H +#define _GL_TEXMAN_H + +//gl_texmgr.h -- fitzquake's texture manager. manages opengl texture images + +#define TEXPREF_NONE 0x0000 +#define TEXPREF_MIPMAP 0x0001 // generate mipmaps +// TEXPREF_NEAREST and TEXPREF_LINEAR aren't supposed to be ORed with TEX_MIPMAP +#define TEXPREF_LINEAR 0x0002 // force linear +#define TEXPREF_NEAREST 0x0004 // force nearest +#define TEXPREF_ALPHA 0x0008 // allow alpha +#define TEXPREF_PAD 0x0010 // allow padding +#define TEXPREF_PERSIST 0x0020 // never free +#define TEXPREF_OVERWRITE 0x0040 // overwrite existing same-name texture +#define TEXPREF_NOPICMIP 0x0080 // always load full-sized +#define TEXPREF_FULLBRIGHT 0x0100 // use fullbright mask palette +#define TEXPREF_NOBRIGHT 0x0200 // use nobright mask palette +#define TEXPREF_CONCHARS 0x0400 // use conchars palette +#define TEXPREF_WARPIMAGE 0x0800 // resize this texture when warpimagesize changes + +enum srcformat {SRC_INDEXED, SRC_LIGHTMAP, SRC_RGBA}; + +typedef uintptr_t src_offset_t; + +typedef struct gltexture_s { +//managed by texture manager + GLuint texnum; + struct gltexture_s *next; + qmodel_t *owner; +//managed by image loading + char name[64]; + unsigned int width; //size of image as it exists in opengl + unsigned int height; //size of image as it exists in opengl + unsigned int flags; + char source_file[MAX_QPATH]; //relative filepath to data source, or "" if source is in memory + src_offset_t source_offset; //byte offset into file, or memory address + enum srcformat source_format; //format of pixel data (indexed, lightmap, or rgba) + unsigned int source_width; //size of image in source data + unsigned int source_height; //size of image in source data + unsigned short source_crc; //generated by source data before modifications + char shirt; //0-13 shirt color, or -1 if never colormapped + char pants; //0-13 pants color, or -1 if never colormapped +//used for rendering + int visframe; //matches r_framecount if texture was bound this frame +} gltexture_t; + +extern gltexture_t *notexture; +extern gltexture_t *nulltexture; + +extern unsigned int d_8to24table[256]; +extern unsigned int d_8to24table_fbright[256]; +extern unsigned int d_8to24table_nobright[256]; +extern unsigned int d_8to24table_conchars[256]; +extern unsigned int d_8to24table_shirt[256]; +extern unsigned int d_8to24table_pants[256]; + +// TEXTURE MANAGER + +float TexMgr_FrameUsage (void); +gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name); +gltexture_t *TexMgr_NewTexture (void); +void TexMgr_FreeTexture (gltexture_t *kill); +void TexMgr_FreeTextures (unsigned int flags, unsigned int mask); +void TexMgr_FreeTexturesForOwner (qmodel_t *owner); +void TexMgr_NewGame (void); +void TexMgr_Init (void); +void TexMgr_DeleteTextureObjects (void); + +// IMAGE LOADING +gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int height, enum srcformat format, + byte *data, const char *source_file, src_offset_t source_offset, unsigned flags); +void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants); +void TexMgr_ReloadImages (void); +void TexMgr_ReloadNobrightImages (void); + +int TexMgr_Pad(int s); +int TexMgr_SafeTextureSize (int s); +int TexMgr_PadConditional (int s); + +// TEXTURE BINDING & TEXTURE UNIT SWITCHING + +void GL_SelectTexture (GLenum target); +void GL_DisableMultitexture (void); //selects texture unit 0 +void GL_EnableMultitexture (void); //selects texture unit 1 +void GL_Bind (gltexture_t *texture); +void GL_ClearBindings (void); + +#endif /* _GL_TEXMAN_H */ + diff --git a/source/gl_vidsdl.c b/source/gl_vidsdl.c new file mode 100644 index 0000000..3efc324 --- /dev/null +++ b/source/gl_vidsdl.c @@ -0,0 +1,2106 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +// gl_vidsdl.c -- SDL GL vid component + +#include "quakedef.h" +#include "cfgfile.h" +#include "bgmusic.h" +#include "resource.h" +#include + +//ericw -- for putting the driver into multithreaded mode +#ifdef __APPLE__ +#include +#endif + +#define MAX_MODE_LIST 600 //johnfitz -- was 30 +#define MAX_BPPS_LIST 5 +#define MAX_RATES_LIST 20 +#define WARP_WIDTH 320 +#define WARP_HEIGHT 200 +#define MAXWIDTH 10000 +#define MAXHEIGHT 10000 + +#define DEFAULT_SDL_FLAGS SDL_OPENGL + +#define DEFAULT_REFRESHRATE 60 + +typedef struct { + int width; + int height; + int refreshrate; + int bpp; +} vmode_t; + +static const char *gl_vendor; +static const char *gl_renderer; +static const char *gl_version; +static int gl_version_major; +static int gl_version_minor; +static const char *gl_extensions; +static char * gl_extensions_nice; + +static vmode_t modelist[MAX_MODE_LIST]; +static int nummodes; + +static qboolean vid_initialized = false; + +static SDL_Window *draw_context; +static SDL_GLContext gl_context; + +static qboolean vid_locked = false; //johnfitz +static qboolean vid_changed = false; + +static void VID_Menu_Init (void); //johnfitz +static void VID_Menu_f (void); //johnfitz +static void VID_MenuDraw (void); +static void VID_MenuKey (int key); + +static void ClearAllStates (void); +static void GL_Init (void); +static void GL_SetupState (void); //johnfitz + +viddef_t vid; // global video state +modestate_t modestate = MS_UNINIT; +qboolean scr_skipupdate; + +qboolean gl_mtexable = false; +qboolean gl_texture_env_combine = false; //johnfitz +qboolean gl_texture_env_add = false; //johnfitz +qboolean gl_swap_control = false; //johnfitz +qboolean gl_anisotropy_able = false; //johnfitz +float gl_max_anisotropy; //johnfitz +qboolean gl_texture_NPOT = false; //ericw +qboolean gl_vbo_able = false; //ericw +qboolean gl_glsl_able = false; //ericw +GLint gl_max_texture_units = 0; //ericw +qboolean gl_glsl_gamma_able = false; //ericw +qboolean gl_glsl_alias_able = false; //ericw +int gl_stencilbits; + +PFNGLMULTITEXCOORD2FARBPROC GL_MTexCoord2fFunc = NULL; //johnfitz +PFNGLACTIVETEXTUREARBPROC GL_SelectTextureFunc = NULL; //johnfitz +PFNGLCLIENTACTIVETEXTUREARBPROC GL_ClientActiveTextureFunc = NULL; //ericw +PFNGLBINDBUFFERARBPROC GL_BindBufferFunc = NULL; //ericw +PFNGLBUFFERDATAARBPROC GL_BufferDataFunc = NULL; //ericw +PFNGLBUFFERSUBDATAARBPROC GL_BufferSubDataFunc = NULL; //ericw +PFNGLDELETEBUFFERSARBPROC GL_DeleteBuffersFunc = NULL; //ericw +PFNGLGENBUFFERSARBPROC GL_GenBuffersFunc = NULL; //ericw + +QS_PFNGLCREATESHADERPROC GL_CreateShaderFunc = NULL; //ericw +QS_PFNGLDELETESHADERPROC GL_DeleteShaderFunc = NULL; //ericw +QS_PFNGLDELETEPROGRAMPROC GL_DeleteProgramFunc = NULL; //ericw +QS_PFNGLSHADERSOURCEPROC GL_ShaderSourceFunc = NULL; //ericw +QS_PFNGLCOMPILESHADERPROC GL_CompileShaderFunc = NULL; //ericw +QS_PFNGLGETSHADERIVPROC GL_GetShaderivFunc = NULL; //ericw +QS_PFNGLGETSHADERINFOLOGPROC GL_GetShaderInfoLogFunc = NULL; //ericw +QS_PFNGLGETPROGRAMIVPROC GL_GetProgramivFunc = NULL; //ericw +QS_PFNGLGETPROGRAMINFOLOGPROC GL_GetProgramInfoLogFunc = NULL; //ericw +QS_PFNGLCREATEPROGRAMPROC GL_CreateProgramFunc = NULL; //ericw +QS_PFNGLATTACHSHADERPROC GL_AttachShaderFunc = NULL; //ericw +QS_PFNGLLINKPROGRAMPROC GL_LinkProgramFunc = NULL; //ericw +QS_PFNGLBINDATTRIBLOCATIONFUNC GL_BindAttribLocationFunc = NULL; //ericw +QS_PFNGLUSEPROGRAMPROC GL_UseProgramFunc = NULL; //ericw +QS_PFNGLGETATTRIBLOCATIONPROC GL_GetAttribLocationFunc = NULL; //ericw +QS_PFNGLVERTEXATTRIBPOINTERPROC GL_VertexAttribPointerFunc = NULL; //ericw +QS_PFNGLENABLEVERTEXATTRIBARRAYPROC GL_EnableVertexAttribArrayFunc = NULL; //ericw +QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC GL_DisableVertexAttribArrayFunc = NULL; //ericw +QS_PFNGLGETUNIFORMLOCATIONPROC GL_GetUniformLocationFunc = NULL; //ericw +QS_PFNGLUNIFORM1IPROC GL_Uniform1iFunc = NULL; //ericw +QS_PFNGLUNIFORM1FPROC GL_Uniform1fFunc = NULL; //ericw +QS_PFNGLUNIFORM3FPROC GL_Uniform3fFunc = NULL; //ericw +QS_PFNGLUNIFORM4FPROC GL_Uniform4fFunc = NULL; //ericw + +//==================================== + +//johnfitz -- new cvars +static cvar_t vid_fullscreen = {"vid_fullscreen", "0", CVAR_ARCHIVE}; // QuakeSpasm, was "1" +static cvar_t vid_width = {"vid_width", "800", CVAR_ARCHIVE}; // QuakeSpasm, was 640 +static cvar_t vid_height = {"vid_height", "600", CVAR_ARCHIVE}; // QuakeSpasm, was 480 +static cvar_t vid_bpp = {"vid_bpp", "16", CVAR_ARCHIVE}; +static cvar_t vid_refreshrate = {"vid_refreshrate", "60", CVAR_ARCHIVE}; +static cvar_t vid_vsync = {"vid_vsync", "0", CVAR_ARCHIVE}; +static cvar_t vid_fsaa = {"vid_fsaa", "0", CVAR_ARCHIVE}; // QuakeSpasm +static cvar_t vid_desktopfullscreen = {"vid_desktopfullscreen", "0", CVAR_ARCHIVE}; // QuakeSpasm +static cvar_t vid_borderless = {"vid_borderless", "0", CVAR_ARCHIVE}; // QuakeSpasm +//johnfitz + +cvar_t vid_gamma = {"gamma", "1", CVAR_ARCHIVE}; //johnfitz -- moved here from view.c +cvar_t vid_contrast = {"contrast", "1", CVAR_ARCHIVE}; //QuakeSpasm, MarkV + +//========================================================================== +// +// HARDWARE GAMMA -- johnfitz +// +//========================================================================== + +#define USE_GAMMA_RAMPS 0 + +#if USE_GAMMA_RAMPS +static unsigned short vid_gamma_red[256]; +static unsigned short vid_gamma_green[256]; +static unsigned short vid_gamma_blue[256]; + +static unsigned short vid_sysgamma_red[256]; +static unsigned short vid_sysgamma_green[256]; +static unsigned short vid_sysgamma_blue[256]; +#endif + +static qboolean gammaworks = false; // whether hw-gamma works +static int fsaa; + +/* +================ +VID_Gamma_SetGamma -- apply gamma correction +================ +*/ +static void VID_Gamma_SetGamma (void) +{ + if (gl_glsl_gamma_able) + return; + + if (draw_context && gammaworks) + { + float value; + + if (vid_gamma.value > (1.0f / GAMMA_MAX)) + value = 1.0f / vid_gamma.value; + else + value = GAMMA_MAX; + +# if USE_GAMMA_RAMPS + if (SDL_SetWindowGammaRamp(draw_context, vid_gamma_red, vid_gamma_green, vid_gamma_blue) != 0) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetWindowGammaRamp\n"); +# else + if (SDL_SetWindowBrightness(draw_context, value) != 0) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetWindowBrightness\n"); +# endif + } +} + +/* +================ +VID_Gamma_Restore -- restore system gamma +================ +*/ +static void VID_Gamma_Restore (void) +{ + if (gl_glsl_gamma_able) + return; + + if (draw_context && gammaworks) + { +# if USE_GAMMA_RAMPS + if (SDL_SetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) != 0) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetWindowGammaRamp\n"); +# else + if (SDL_SetWindowBrightness(draw_context, 1) != 0) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetWindowBrightness\n"); +# endif + } +} + +/* +================ +VID_Gamma_Shutdown -- called on exit +================ +*/ +static void VID_Gamma_Shutdown (void) +{ + VID_Gamma_Restore (); +} + +/* +================ +VID_Gamma_f -- callback when the cvar changes +================ +*/ +static void VID_Gamma_f (cvar_t *var) +{ + if (gl_glsl_gamma_able) + return; + +#if USE_GAMMA_RAMPS + int i; + + for (i = 0; i < 256; i++) + { + vid_gamma_red[i] = + CLAMP(0, (int) ((255 * pow((i + 0.5)/255.5, vid_gamma.value) + 0.5) * vid_contrast.value), 255) << 8; + vid_gamma_green[i] = vid_gamma_red[i]; + vid_gamma_blue[i] = vid_gamma_red[i]; + } +#endif + VID_Gamma_SetGamma (); +} + +/* +================ +VID_Gamma_Init -- call on init +================ +*/ +static void VID_Gamma_Init (void) +{ + Cvar_RegisterVariable (&vid_gamma); + Cvar_RegisterVariable (&vid_contrast); + Cvar_SetCallback (&vid_gamma, VID_Gamma_f); + Cvar_SetCallback (&vid_contrast, VID_Gamma_f); + + if (gl_glsl_gamma_able) + return; + +# if USE_GAMMA_RAMPS + gammaworks = (SDL_GetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); + if (gammaworks) + gammaworks = (SDL_SetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); +# else + gammaworks = (SDL_SetWindowBrightness(draw_context, 1) == 0); +# endif + + if (!gammaworks) + Con_SafePrintf("gamma adjustment not available\n"); +} + +/* +====================== +VID_GetCurrentWidth +====================== +*/ +static int VID_GetCurrentWidth (void) +{ + int w; + SDL_GetWindowSize(draw_context, &w, NULL); + return w; +} + +/* +======================= +VID_GetCurrentHeight +======================= +*/ +static int VID_GetCurrentHeight (void) +{ + int h; + SDL_GetWindowSize(draw_context, NULL, &h); + return h; +} + +/* +==================== +VID_GetCurrentRefreshRate +==================== +*/ +static int VID_GetCurrentRefreshRate (void) +{ + SDL_DisplayMode mode; + int current_display; + + current_display = SDL_GetWindowDisplayIndex(draw_context); + + if (0 != SDL_GetCurrentDisplayMode(current_display, &mode)) + return DEFAULT_REFRESHRATE; + + return mode.refresh_rate; +} + + +/* +==================== +VID_GetCurrentBPP +==================== +*/ +static int VID_GetCurrentBPP (void) +{ + const Uint32 pixelFormat = SDL_GetWindowPixelFormat(draw_context); + return SDL_BITSPERPIXEL(pixelFormat); +} + +/* +==================== +VID_GetFullscreen + +returns true if we are in regular fullscreen or "desktop fullscren" +==================== +*/ +static qboolean VID_GetFullscreen (void) +{ + return (SDL_GetWindowFlags(draw_context) & SDL_WINDOW_FULLSCREEN) != 0; +} + +/* +==================== +VID_GetDesktopFullscreen + +returns true if we are specifically in "desktop fullscreen" mode +==================== +*/ +static qboolean VID_GetDesktopFullscreen (void) +{ + return (SDL_GetWindowFlags(draw_context) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP; +} + +/* +==================== +VID_GetVSync +==================== +*/ +static qboolean VID_GetVSync (void) +{ + return SDL_GL_GetSwapInterval() == 1; +} + +/* +==================== +VID_GetWindow + +used by pl_win.c +==================== +*/ +void *VID_GetWindow (void) +{ + return draw_context; +} + +/* +==================== +VID_HasMouseOrInputFocus +==================== +*/ +qboolean VID_HasMouseOrInputFocus (void) +{ + return (SDL_GetWindowFlags(draw_context) & (SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS)) != 0; +} + +/* +==================== +VID_IsMinimized +==================== +*/ +qboolean VID_IsMinimized (void) +{ + return !(SDL_GetWindowFlags(draw_context) & SDL_WINDOW_SHOWN); +} + +/* +================ +VID_SDL2_GetDisplayMode + +Returns a pointer to a statically allocated SDL_DisplayMode structure +if there is one with the requested params on the default display. +Otherwise returns NULL. + +This is passed to SDL_SetWindowDisplayMode to specify a pixel format +with the requested bpp. If we didn't care about bpp we could just pass NULL. +================ +*/ +static SDL_DisplayMode *VID_SDL2_GetDisplayMode(int width, int height, int refreshrate, int bpp) +{ + static SDL_DisplayMode mode; + const int sdlmodes = SDL_GetNumDisplayModes(0); + int i; + + for (i = 0; i < sdlmodes; i++) + { + if (SDL_GetDisplayMode(0, i, &mode) != 0) + continue; + + if (mode.w == width && mode.h == height + && SDL_BITSPERPIXEL(mode.format) == bpp + && mode.refresh_rate == refreshrate) + { + return &mode; + } + } + return NULL; +} + +/* +================ +VID_ValidMode +================ +*/ +static qboolean VID_ValidMode (int width, int height, int refreshrate, int bpp, qboolean fullscreen) +{ +// ignore width / height / bpp if vid_desktopfullscreen is enabled + if (fullscreen && vid_desktopfullscreen.value) + return true; + + if (width < 320) + return false; + + if (height < 200) + return false; + + if (fullscreen && VID_SDL2_GetDisplayMode(width, height, refreshrate, bpp) == NULL) + bpp = 0; + + switch (bpp) + { + case 16: + case 24: + case 32: + break; + default: + return false; + } + + return true; +} + +/* +================ +VID_SetMode +================ +*/ +static qboolean VID_SetMode (int width, int height, int refreshrate, int bpp, qboolean fullscreen) +{ + int temp; + Uint32 flags; + char caption[50]; + int depthbits, stencilbits; + int fsaa_obtained; + int previous_display; + + // so Con_Printfs don't mess us up by forcing vid and snd updates + temp = scr_disabled_for_loading; + scr_disabled_for_loading = true; + + CDAudio_Pause (); + BGM_Pause (); + + /* z-buffer depth */ + if (bpp == 16) + { + depthbits = 16; + stencilbits = 0; + } + else + { + depthbits = 24; + stencilbits = 8; + } + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthbits); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilbits); + + /* fsaa */ + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, fsaa > 0 ? 1 : 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa); + + q_snprintf(caption, sizeof(caption), "QuakeSpasm " QUAKESPASM_VER_STRING); + + /* Create the window if needed, hidden */ + if (!draw_context) + { + flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN; + + if (vid_borderless.value) + flags |= SDL_WINDOW_BORDERLESS; + + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + if (!draw_context) { // scale back fsaa + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) { // scale back SDL_GL_DEPTH_SIZE + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) { // scale back SDL_GL_STENCIL_SIZE + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) + Sys_Error ("Couldn't create window"); + + previous_display = -1; + } + else + { + previous_display = SDL_GetWindowDisplayIndex(draw_context); + } + + /* Ensure the window is not fullscreen */ + if (VID_GetFullscreen ()) + { + if (SDL_SetWindowFullscreen (draw_context, 0) != 0) + Sys_Error("Couldn't set fullscreen state mode"); + } + + /* Set window size and display mode */ + SDL_SetWindowSize (draw_context, width, height); + if (previous_display >= 0) + SDL_SetWindowPosition (draw_context, SDL_WINDOWPOS_CENTERED_DISPLAY(previous_display), SDL_WINDOWPOS_CENTERED_DISPLAY(previous_display)); + else + SDL_SetWindowPosition(draw_context, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_SetWindowDisplayMode (draw_context, VID_SDL2_GetDisplayMode(width, height, refreshrate, bpp)); + SDL_SetWindowBordered (draw_context, vid_borderless.value ? SDL_FALSE : SDL_TRUE); + + /* Make window fullscreen if needed, and show the window */ + + if (fullscreen) { + Uint32 flags = vid_desktopfullscreen.value ? + SDL_WINDOW_FULLSCREEN_DESKTOP : + SDL_WINDOW_FULLSCREEN; + if (SDL_SetWindowFullscreen (draw_context, flags) != 0) + Sys_Error ("Couldn't set fullscreen state mode"); + } + + SDL_ShowWindow (draw_context); + + /* Create GL context if needed */ + if (!gl_context) { + gl_context = SDL_GL_CreateContext(draw_context); + if (!gl_context) + Sys_Error("Couldn't create GL context"); + } + + gl_swap_control = true; + if (SDL_GL_SetSwapInterval ((vid_vsync.value) ? 1 : 0) == -1) + gl_swap_control = false; + + vid.width = VID_GetCurrentWidth(); + vid.height = VID_GetCurrentHeight(); + vid.conwidth = vid.width & 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; + vid.numpages = 2; + +// read the obtained z-buffer depth + if (SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthbits) == -1) + depthbits = 0; + +// read obtained fsaa samples + if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fsaa_obtained) == -1) + fsaa_obtained = 0; + +// read stencil bits + if (SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &gl_stencilbits) == -1) + gl_stencilbits = 0; + + modestate = VID_GetFullscreen() ? MS_FULLSCREEN : MS_WINDOWED; + + CDAudio_Resume (); + BGM_Resume (); + scr_disabled_for_loading = temp; + +// fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + + Con_SafePrintf ("Video mode %dx%dx%d %dHz (%d-bit z-buffer, %dx FSAA) initialized\n", + VID_GetCurrentWidth(), + VID_GetCurrentHeight(), + VID_GetCurrentBPP(), + VID_GetCurrentRefreshRate(), + depthbits, + fsaa_obtained); + + vid.recalc_refdef = 1; + +// no pending changes + vid_changed = false; + + return true; +} + +/* +=================== +VID_Changed_f -- kristian -- notify us that a value has changed that requires a vid_restart +=================== +*/ +static void VID_Changed_f (cvar_t *var) +{ + vid_changed = true; +} + +/* +=================== +VID_Restart -- johnfitz -- change video modes on the fly +=================== +*/ +static void VID_Restart (void) +{ + int width, height, refreshrate, bpp; + qboolean fullscreen; + + if (vid_locked || !vid_changed) + return; + + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = vid_fullscreen.value ? true : false; + +// +// validate new mode +// + if (!VID_ValidMode (width, height, refreshrate, bpp, fullscreen)) + { + Con_Printf ("%dx%dx%d %dHz %s is not a valid mode\n", + width, height, bpp, refreshrate, fullscreen? "fullscreen" : "windowed"); + return; + } + +// ericw -- OS X, SDL1: textures, VBO's invalid after mode change +// OS X, SDL2: still valid after mode change +// To handle both cases, delete all GL objects (textures, VBO, GLSL) now. +// We must not interleave deleting the old objects with creating new ones, because +// one of the new objects could be given the same ID as an invalid handle +// which is later deleted. + + TexMgr_DeleteTextureObjects (); + GLSLGamma_DeleteTexture (); + R_ScaleView_DeleteTexture (); + R_DeleteShaders (); + GL_DeleteBModelVertexBuffer (); + GLMesh_DeleteVertexBuffers (); + +// +// set new mode +// + VID_SetMode (width, height, refreshrate, bpp, fullscreen); + + GL_Init (); + TexMgr_ReloadImages (); + GL_BuildBModelVertexBuffer (); + GLMesh_LoadVertexBuffers (); + GL_SetupState (); + Fog_SetupState (); + + //warpimages needs to be recalculated + TexMgr_RecalcWarpImageSize (); + + //conwidth and conheight need to be recalculated + vid.conwidth = (scr_conwidth.value > 0) ? (int)scr_conwidth.value : (scr_conscale.value > 0) ? (int)(vid.width/scr_conscale.value) : vid.width; + vid.conwidth = CLAMP (320, vid.conwidth, vid.width); + vid.conwidth &= 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; +// +// keep cvars in line with actual mode +// + VID_SyncCvars(); +// +// update mouse grab +// + if (key_dest == key_console || key_dest == key_menu) + { + if (modestate == MS_WINDOWED) + IN_Deactivate(true); + else if (modestate == MS_FULLSCREEN) + IN_Activate(); + } +} + +/* +================ +VID_Test -- johnfitz -- like vid_restart, but asks for confirmation after switching modes +================ +*/ +static void VID_Test (void) +{ + int old_width, old_height, old_refreshrate, old_bpp, old_fullscreen; + + if (vid_locked || !vid_changed) + return; +// +// now try the switch +// + old_width = VID_GetCurrentWidth(); + old_height = VID_GetCurrentHeight(); + old_refreshrate = VID_GetCurrentRefreshRate(); + old_bpp = VID_GetCurrentBPP(); + old_fullscreen = VID_GetFullscreen() ? true : false; + + VID_Restart (); + + //pop up confirmation dialoge + if (!SCR_ModalMessage("Would you like to keep this\nvideo mode? (y/n)\n", 5.0f)) + { + //revert cvars and mode + Cvar_SetValueQuick (&vid_width, old_width); + Cvar_SetValueQuick (&vid_height, old_height); + Cvar_SetValueQuick (&vid_refreshrate, old_refreshrate); + Cvar_SetValueQuick (&vid_bpp, old_bpp); + Cvar_SetQuick (&vid_fullscreen, old_fullscreen ? "1" : "0"); + VID_Restart (); + } +} + +/* +================ +VID_Unlock -- johnfitz +================ +*/ +static void VID_Unlock (void) +{ + vid_locked = false; + VID_SyncCvars(); +} + +/* +================ +VID_Lock -- ericw + +Subsequent changes to vid_* mode settings, and vid_restart commands, will +be ignored until the "vid_unlock" command is run. + +Used when changing gamedirs so the current settings override what was saved +in the config.cfg. +================ +*/ +void VID_Lock (void) +{ + vid_locked = true; +} + +//============================================================================== +// +// OPENGL STUFF +// +//============================================================================== + +/* +=============== +GL_MakeNiceExtensionsList -- johnfitz +=============== +*/ +static char *GL_MakeNiceExtensionsList (const char *in) +{ + char *copy, *token, *out; + int i, count; + + if (!in) return Z_Strdup("(none)"); + + //each space will be replaced by 4 chars, so count the spaces before we malloc + for (i = 0, count = 1; i < (int) strlen(in); i++) + { + if (in[i] == ' ') + count++; + } + + out = (char *) Z_Malloc (strlen(in) + count*3 + 1); //usually about 1-2k + out[0] = 0; + + copy = (char *) Z_Strdup(in); + for (token = strtok(copy, " "); token; token = strtok(NULL, " ")) + { + strcat(out, "\n "); + strcat(out, token); + } + + Z_Free (copy); + return out; +} + +/* +=============== +GL_Info_f -- johnfitz +=============== +*/ +static void GL_Info_f (void) +{ + Con_SafePrintf ("GL_VENDOR: %s\n", gl_vendor); + Con_SafePrintf ("GL_RENDERER: %s\n", gl_renderer); + Con_SafePrintf ("GL_VERSION: %s\n", gl_version); + Con_Printf ("GL_EXTENSIONS: %s\n", gl_extensions_nice); +} + +/* +=============== +GL_CheckExtensions +=============== +*/ +static qboolean GL_ParseExtensionList (const char *list, const char *name) +{ + const char *start; + const char *where, *terminator; + + if (!list || !name || !*name) + return false; + if (strchr(name, ' ') != NULL) + return false; // extension names must not have spaces + + start = list; + while (1) { + where = strstr (start, name); + if (!where) + break; + terminator = where + strlen (name); + if (where == start || where[-1] == ' ') + if (*terminator == ' ' || *terminator == '\0') + return true; + start = terminator; + } + return false; +} + +static void GL_CheckExtensions (void) +{ + int swap_control; + + // ARB_vertex_buffer_object + // + if (COM_CheckParm("-novbo")) + Con_Warning ("Vertex buffer objects disabled at command line\n"); + else if (gl_version_major < 1 || (gl_version_major == 1 && gl_version_minor < 5)) + Con_Warning ("OpenGL version < 1.5, skipping ARB_vertex_buffer_object check\n"); + else + { + GL_BindBufferFunc = (PFNGLBINDBUFFERARBPROC) SDL_GL_GetProcAddress("glBindBufferARB"); + GL_BufferDataFunc = (PFNGLBUFFERDATAARBPROC) SDL_GL_GetProcAddress("glBufferDataARB"); + GL_BufferSubDataFunc = (PFNGLBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glBufferSubDataARB"); + GL_DeleteBuffersFunc = (PFNGLDELETEBUFFERSARBPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB"); + GL_GenBuffersFunc = (PFNGLGENBUFFERSARBPROC) SDL_GL_GetProcAddress("glGenBuffersARB"); + if (GL_BindBufferFunc && GL_BufferDataFunc && GL_BufferSubDataFunc && GL_DeleteBuffersFunc && GL_GenBuffersFunc) + { + Con_Printf("FOUND: ARB_vertex_buffer_object\n"); + gl_vbo_able = true; + } + else + { + Con_Warning ("ARB_vertex_buffer_object not available\n"); + } + } + + // multitexture + // + if (COM_CheckParm("-nomtex")) + Con_Warning ("Mutitexture disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_multitexture")) + { + GL_MTexCoord2fFunc = (PFNGLMULTITEXCOORD2FARBPROC) SDL_GL_GetProcAddress("glMultiTexCoord2fARB"); + GL_SelectTextureFunc = (PFNGLACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glActiveTextureARB"); + GL_ClientActiveTextureFunc = (PFNGLCLIENTACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glClientActiveTextureARB"); + if (GL_MTexCoord2fFunc && GL_SelectTextureFunc && GL_ClientActiveTextureFunc) + { + Con_Printf("FOUND: ARB_multitexture\n"); + gl_mtexable = true; + + glGetIntegerv(GL_MAX_TEXTURE_UNITS, &gl_max_texture_units); + Con_Printf("GL_MAX_TEXTURE_UNITS: %d\n", (int)gl_max_texture_units); + } + else + { + Con_Warning ("Couldn't link to multitexture functions\n"); + } + } + else + { + Con_Warning ("multitexture not supported (extension not found)\n"); + } + + // texture_env_combine + // + if (COM_CheckParm("-nocombine")) + Con_Warning ("texture_env_combine disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_env_combine")) + { + Con_Printf("FOUND: ARB_texture_env_combine\n"); + gl_texture_env_combine = true; + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_env_combine")) + { + Con_Printf("FOUND: EXT_texture_env_combine\n"); + gl_texture_env_combine = true; + } + else + { + Con_Warning ("texture_env_combine not supported\n"); + } + + // texture_env_add + // + if (COM_CheckParm("-noadd")) + Con_Warning ("texture_env_add disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_env_add")) + { + Con_Printf("FOUND: ARB_texture_env_add\n"); + gl_texture_env_add = true; + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_env_add")) + { + Con_Printf("FOUND: EXT_texture_env_add\n"); + gl_texture_env_add = true; + } + else + { + Con_Warning ("texture_env_add not supported\n"); + } + + // swap control + // + if (!gl_swap_control) + { + Con_Warning ("vertical sync not supported (SDL_GL_SetSwapInterval failed)\n"); + } + else if ((swap_control = SDL_GL_GetSwapInterval()) == -1) + { + gl_swap_control = false; + Con_Warning ("vertical sync not supported (SDL_GL_GetSwapInterval failed)\n"); + } + else if ((vid_vsync.value && swap_control != 1) || (!vid_vsync.value && swap_control != 0)) + { + gl_swap_control = false; + Con_Warning ("vertical sync not supported (swap_control doesn't match vid_vsync)\n"); + } + else + { + Con_Printf("FOUND: SDL_GL_SetSwapInterval\n"); + } + + // anisotropic filtering + // + if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_filter_anisotropic")) + { + float test1,test2; + GLuint tex; + + // test to make sure we really have control over it + // 1.0 and 2.0 should always be legal values + glGenTextures(1, &tex); + glBindTexture (GL_TEXTURE_2D, tex); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + glGetTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &test1); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f); + glGetTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &test2); + glDeleteTextures(1, &tex); + + if (test1 == 1 && test2 == 2) + { + Con_Printf("FOUND: EXT_texture_filter_anisotropic\n"); + gl_anisotropy_able = true; + } + else + { + Con_Warning ("anisotropic filtering locked by driver. Current driver setting is %f\n", test1); + } + + //get max value either way, so the menu and stuff know it + glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_max_anisotropy); + if (gl_max_anisotropy < 2) + { + gl_anisotropy_able = false; + gl_max_anisotropy = 1; + Con_Warning ("anisotropic filtering broken: disabled\n"); + } + } + else + { + gl_max_anisotropy = 1; + Con_Warning ("texture_filter_anisotropic not supported\n"); + } + + // texture_non_power_of_two + // + if (COM_CheckParm("-notexturenpot")) + Con_Warning ("texture_non_power_of_two disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_non_power_of_two")) + { + Con_Printf("FOUND: ARB_texture_non_power_of_two\n"); + gl_texture_NPOT = true; + } + else + { + Con_Warning ("texture_non_power_of_two not supported\n"); + } + + // GLSL + // + if (COM_CheckParm("-noglsl")) + Con_Warning ("GLSL disabled at command line\n"); + else if (gl_version_major >= 2) + { + GL_CreateShaderFunc = (QS_PFNGLCREATESHADERPROC) SDL_GL_GetProcAddress("glCreateShader"); + GL_DeleteShaderFunc = (QS_PFNGLDELETESHADERPROC) SDL_GL_GetProcAddress("glDeleteShader"); + GL_DeleteProgramFunc = (QS_PFNGLDELETEPROGRAMPROC) SDL_GL_GetProcAddress("glDeleteProgram"); + GL_ShaderSourceFunc = (QS_PFNGLSHADERSOURCEPROC) SDL_GL_GetProcAddress("glShaderSource"); + GL_CompileShaderFunc = (QS_PFNGLCOMPILESHADERPROC) SDL_GL_GetProcAddress("glCompileShader"); + GL_GetShaderivFunc = (QS_PFNGLGETSHADERIVPROC) SDL_GL_GetProcAddress("glGetShaderiv"); + GL_GetShaderInfoLogFunc = (QS_PFNGLGETSHADERINFOLOGPROC) SDL_GL_GetProcAddress("glGetShaderInfoLog"); + GL_GetProgramivFunc = (QS_PFNGLGETPROGRAMIVPROC) SDL_GL_GetProcAddress("glGetProgramiv"); + GL_GetProgramInfoLogFunc = (QS_PFNGLGETPROGRAMINFOLOGPROC) SDL_GL_GetProcAddress("glGetProgramInfoLog"); + GL_CreateProgramFunc = (QS_PFNGLCREATEPROGRAMPROC) SDL_GL_GetProcAddress("glCreateProgram"); + GL_AttachShaderFunc = (QS_PFNGLATTACHSHADERPROC) SDL_GL_GetProcAddress("glAttachShader"); + GL_LinkProgramFunc = (QS_PFNGLLINKPROGRAMPROC) SDL_GL_GetProcAddress("glLinkProgram"); + GL_BindAttribLocationFunc = (QS_PFNGLBINDATTRIBLOCATIONFUNC) SDL_GL_GetProcAddress("glBindAttribLocation"); + GL_UseProgramFunc = (QS_PFNGLUSEPROGRAMPROC) SDL_GL_GetProcAddress("glUseProgram"); + GL_GetAttribLocationFunc = (QS_PFNGLGETATTRIBLOCATIONPROC) SDL_GL_GetProcAddress("glGetAttribLocation"); + GL_VertexAttribPointerFunc = (QS_PFNGLVERTEXATTRIBPOINTERPROC) SDL_GL_GetProcAddress("glVertexAttribPointer"); + GL_EnableVertexAttribArrayFunc = (QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glEnableVertexAttribArray"); + GL_DisableVertexAttribArrayFunc = (QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glDisableVertexAttribArray"); + GL_GetUniformLocationFunc = (QS_PFNGLGETUNIFORMLOCATIONPROC) SDL_GL_GetProcAddress("glGetUniformLocation"); + GL_Uniform1iFunc = (QS_PFNGLUNIFORM1IPROC) SDL_GL_GetProcAddress("glUniform1i"); + GL_Uniform1fFunc = (QS_PFNGLUNIFORM1FPROC) SDL_GL_GetProcAddress("glUniform1f"); + GL_Uniform3fFunc = (QS_PFNGLUNIFORM3FPROC) SDL_GL_GetProcAddress("glUniform3f"); + GL_Uniform4fFunc = (QS_PFNGLUNIFORM4FPROC) SDL_GL_GetProcAddress("glUniform4f"); + + if (GL_CreateShaderFunc && + GL_DeleteShaderFunc && + GL_DeleteProgramFunc && + GL_ShaderSourceFunc && + GL_CompileShaderFunc && + GL_GetShaderivFunc && + GL_GetShaderInfoLogFunc && + GL_GetProgramivFunc && + GL_GetProgramInfoLogFunc && + GL_CreateProgramFunc && + GL_AttachShaderFunc && + GL_LinkProgramFunc && + GL_BindAttribLocationFunc && + GL_UseProgramFunc && + GL_GetAttribLocationFunc && + GL_VertexAttribPointerFunc && + GL_EnableVertexAttribArrayFunc && + GL_DisableVertexAttribArrayFunc && + GL_GetUniformLocationFunc && + GL_Uniform1iFunc && + GL_Uniform1fFunc && + GL_Uniform3fFunc && + GL_Uniform4fFunc) + { + Con_Printf("FOUND: GLSL\n"); + gl_glsl_able = true; + } + else + { + Con_Warning ("GLSL not available\n"); + } + } + else + { + Con_Warning ("OpenGL version < 2, GLSL not available\n"); + } + + // GLSL gamma + // + if (COM_CheckParm("-noglslgamma")) + Con_Warning ("GLSL gamma disabled at command line\n"); + else if (gl_glsl_able) + { + gl_glsl_gamma_able = true; + } + else + { + Con_Warning ("GLSL gamma not available, using hardware gamma\n"); + } + + // GLSL alias model rendering + // + if (COM_CheckParm("-noglslalias")) + Con_Warning ("GLSL alias model rendering disabled at command line\n"); + else if (gl_glsl_able && gl_vbo_able && gl_max_texture_units >= 3) + { + gl_glsl_alias_able = true; + } + else + { + Con_Warning ("GLSL alias model rendering not available, using Fitz renderer\n"); + } +} + +/* +=============== +GL_SetupState -- johnfitz + +does all the stuff from GL_Init that needs to be done every time a new GL render context is created +=============== +*/ +static void GL_SetupState (void) +{ + glClearColor (0.15,0.15,0.15,0); //johnfitz -- originally 1,0,0,0 + glCullFace(GL_BACK); //johnfitz -- glquake used CCW with backwards culling -- let's do it right + glFrontFace(GL_CW); //johnfitz -- glquake used CCW with backwards culling -- let's do it right + glEnable(GL_TEXTURE_2D); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.666); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glShadeModel (GL_FLAT); + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //johnfitz + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glDepthRange (0, 1); //johnfitz -- moved here becuase gl_ztrick is gone. + glDepthFunc (GL_LEQUAL); //johnfitz -- moved here becuase gl_ztrick is gone. +} + +/* +=============== +GL_Init +=============== +*/ +static void GL_Init (void) +{ + gl_vendor = (const char *) glGetString (GL_VENDOR); + gl_renderer = (const char *) glGetString (GL_RENDERER); + gl_version = (const char *) glGetString (GL_VERSION); + gl_extensions = (const char *) glGetString (GL_EXTENSIONS); + + Con_SafePrintf ("GL_VENDOR: %s\n", gl_vendor); + Con_SafePrintf ("GL_RENDERER: %s\n", gl_renderer); + Con_SafePrintf ("GL_VERSION: %s\n", gl_version); + + if (gl_version == NULL || sscanf(gl_version, "%d.%d", &gl_version_major, &gl_version_minor) < 2) + { + gl_version_major = 0; + gl_version_minor = 0; + } + + if (gl_extensions_nice != NULL) + Z_Free (gl_extensions_nice); + gl_extensions_nice = GL_MakeNiceExtensionsList (gl_extensions); + + GL_CheckExtensions (); //johnfitz + +#ifdef __APPLE__ + // ericw -- enable multi-threaded OpenGL, gives a decent FPS boost. + // https://developer.apple.com/library/mac/technotes/tn2085/ + if (host_parms->numcpus > 1 && + kCGLNoError != CGLEnable(CGLGetCurrentContext(), kCGLCEMPEngine)) + { + Con_Warning ("Couldn't enable multi-threaded OpenGL"); + } +#endif + + //johnfitz -- intel video workarounds from Baker + if (!strcmp(gl_vendor, "Intel")) + { + Con_Printf ("Intel Display Adapter detected, enabling gl_clear\n"); + Cbuf_AddText ("gl_clear 1"); + } + //johnfitz + + GLAlias_CreateShaders (); + GLWorld_CreateShaders (); + GL_ClearBufferBindings (); +} + +/* +================= +GL_BeginRendering -- sets values of glx, gly, glwidth, glheight +================= +*/ +void GL_BeginRendering (int *x, int *y, int *width, int *height) +{ + *x = *y = 0; + *width = vid.width; + *height = vid.height; +} + +/* +================= +GL_EndRendering +================= +*/ +void GL_EndRendering (void) +{ + if (!scr_skipupdate) + { + SDL_GL_SwapWindow(draw_context); + } +} + + +void VID_Shutdown (void) +{ + if (vid_initialized) + { + VID_Gamma_Shutdown (); //johnfitz + + SDL_QuitSubSystem(SDL_INIT_VIDEO); + draw_context = NULL; + gl_context = NULL; + PL_VID_Shutdown(); + } +} + +/* +=================================================================== + +MAIN WINDOW + +=================================================================== +*/ + +/* +================ +ClearAllStates +================ +*/ +static void ClearAllStates (void) +{ + Key_ClearStates (); + IN_ClearStates (); +} + + +//========================================================================== +// +// COMMANDS +// +//========================================================================== + +/* +================= +VID_DescribeCurrentMode_f +================= +*/ +static void VID_DescribeCurrentMode_f (void) +{ + if (draw_context) + Con_Printf("%dx%dx%d %dHz %s\n", + VID_GetCurrentWidth(), + VID_GetCurrentHeight(), + VID_GetCurrentBPP(), + VID_GetCurrentRefreshRate(), + VID_GetFullscreen() ? "fullscreen" : "windowed"); +} + +/* +================= +VID_DescribeModes_f -- johnfitz -- changed formatting, and added refresh rates after each mode. +================= +*/ +static void VID_DescribeModes_f (void) +{ + int i; + int lastwidth, lastheight, lastbpp, count; + + lastwidth = lastheight = lastbpp = count = 0; + + for (i = 0; i < nummodes; i++) + { + if (lastwidth != modelist[i].width || lastheight != modelist[i].height || lastbpp != modelist[i].bpp) + { + if (count > 0) + Con_SafePrintf ("\n"); + Con_SafePrintf (" %4i x %4i x %i : %i", modelist[i].width, modelist[i].height, modelist[i].bpp, modelist[i].refreshrate); + lastwidth = modelist[i].width; + lastheight = modelist[i].height; + lastbpp = modelist[i].bpp; + count++; + } + } + Con_Printf ("\n%i modes\n", count); +} + +/* +=================== +VID_FSAA_f -- ericw -- warn that vid_fsaa requires engine restart +=================== +*/ +static void VID_FSAA_f (cvar_t *var) +{ + // don't print the warning if vid_fsaa is set during startup + if (vid_initialized) + Con_Printf("%s %d requires engine restart to take effect\n", var->name, (int)var->value); +} + +//========================================================================== +// +// INIT +// +//========================================================================== + +/* +================= +VID_InitModelist +================= +*/ +static void VID_InitModelist (void) +{ + const int sdlmodes = SDL_GetNumDisplayModes(0); + int i; + + nummodes = 0; + for (i = 0; i < sdlmodes; i++) + { + SDL_DisplayMode mode; + + if (nummodes >= MAX_MODE_LIST) + break; + if (SDL_GetDisplayMode(0, i, &mode) == 0) + { + modelist[nummodes].width = mode.w; + modelist[nummodes].height = mode.h; + modelist[nummodes].bpp = SDL_BITSPERPIXEL(mode.format); + modelist[nummodes].refreshrate = mode.refresh_rate; + nummodes++; + } + } +} + +/* +=================== +VID_Init +=================== +*/ +void VID_Init (void) +{ + static char vid_center[] = "SDL_VIDEO_CENTERED=center"; + int p, width, height, refreshrate, bpp; + int display_width, display_height, display_refreshrate, display_bpp; + qboolean fullscreen; + const char *read_vars[] = { "vid_fullscreen", + "vid_width", + "vid_height", + "vid_refreshrate", + "vid_bpp", + "vid_vsync", + "vid_fsaa", + "vid_desktopfullscreen", + "vid_borderless"}; +#define num_readvars ( sizeof(read_vars)/sizeof(read_vars[0]) ) + + Cvar_RegisterVariable (&vid_fullscreen); //johnfitz + Cvar_RegisterVariable (&vid_width); //johnfitz + Cvar_RegisterVariable (&vid_height); //johnfitz + Cvar_RegisterVariable (&vid_refreshrate); //johnfitz + Cvar_RegisterVariable (&vid_bpp); //johnfitz + Cvar_RegisterVariable (&vid_vsync); //johnfitz + Cvar_RegisterVariable (&vid_fsaa); //QuakeSpasm + Cvar_RegisterVariable (&vid_desktopfullscreen); //QuakeSpasm + Cvar_RegisterVariable (&vid_borderless); //QuakeSpasm + Cvar_SetCallback (&vid_fullscreen, VID_Changed_f); + Cvar_SetCallback (&vid_width, VID_Changed_f); + Cvar_SetCallback (&vid_height, VID_Changed_f); + Cvar_SetCallback (&vid_refreshrate, VID_Changed_f); + Cvar_SetCallback (&vid_bpp, VID_Changed_f); + Cvar_SetCallback (&vid_vsync, VID_Changed_f); + Cvar_SetCallback (&vid_fsaa, VID_FSAA_f); + Cvar_SetCallback (&vid_desktopfullscreen, VID_Changed_f); + Cvar_SetCallback (&vid_borderless, VID_Changed_f); + + Cmd_AddCommand ("vid_unlock", VID_Unlock); //johnfitz + Cmd_AddCommand ("vid_restart", VID_Restart); //johnfitz + Cmd_AddCommand ("vid_test", VID_Test); //johnfitz + Cmd_AddCommand ("vid_describecurrentmode", VID_DescribeCurrentMode_f); + Cmd_AddCommand ("vid_describemodes", VID_DescribeModes_f); + + putenv (vid_center); /* SDL_putenv is problematic in versions <= 1.2.9 */ + + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + Sys_Error("Couldn't init SDL video: %s", SDL_GetError()); + + { + SDL_DisplayMode mode; + if (SDL_GetDesktopDisplayMode(0, &mode) != 0) + Sys_Error("Could not get desktop display mode"); + + display_width = mode.w; + display_height = mode.h; + display_refreshrate = mode.refresh_rate; + display_bpp = SDL_BITSPERPIXEL(mode.format); + } + + Cvar_SetValueQuick (&vid_bpp, (float)display_bpp); + + if (CFG_OpenConfig("config.cfg") == 0) + { + CFG_ReadCvars(read_vars, num_readvars); + CFG_CloseConfig(); + } + CFG_ReadCvarOverrides(read_vars, num_readvars); + + VID_InitModelist(); + + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = (int)vid_fullscreen.value; + fsaa = (int)vid_fsaa.value; + + if (COM_CheckParm("-current")) + { + width = display_width; + height = display_height; + refreshrate = display_refreshrate; + bpp = display_bpp; + fullscreen = true; + } + else + { + p = COM_CheckParm("-width"); + if (p && p < com_argc-1) + { + width = Q_atoi(com_argv[p+1]); + + if(!COM_CheckParm("-height")) + height = width * 3 / 4; + } + + p = COM_CheckParm("-height"); + if (p && p < com_argc-1) + { + height = Q_atoi(com_argv[p+1]); + + if(!COM_CheckParm("-width")) + width = height * 4 / 3; + } + + p = COM_CheckParm("-refreshrate"); + if (p && p < com_argc-1) + refreshrate = Q_atoi(com_argv[p+1]); + + p = COM_CheckParm("-bpp"); + if (p && p < com_argc-1) + bpp = Q_atoi(com_argv[p+1]); + + if (COM_CheckParm("-window") || COM_CheckParm("-w")) + fullscreen = false; + else if (COM_CheckParm("-fullscreen") || COM_CheckParm("-f")) + fullscreen = true; + } + + p = COM_CheckParm ("-fsaa"); + if (p && p < com_argc-1) + fsaa = atoi(com_argv[p+1]); + + if (!VID_ValidMode(width, height, refreshrate, bpp, fullscreen)) + { + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = (int)vid_fullscreen.value; + } + + if (!VID_ValidMode(width, height, refreshrate, bpp, fullscreen)) + { + width = 640; + height = 480; + refreshrate = display_refreshrate; + bpp = display_bpp; + fullscreen = false; + } + + vid_initialized = true; + + vid.maxwarpwidth = WARP_WIDTH; + vid.maxwarpheight = WARP_HEIGHT; + vid.colormap = host_colormap; + vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048)); + + // set window icon + PL_SetWindowIcon(); + + VID_SetMode (width, height, refreshrate, bpp, fullscreen); + + GL_Init (); + GL_SetupState (); + Cmd_AddCommand ("gl_info", GL_Info_f); //johnfitz + + //johnfitz -- removed code creating "glquake" subdirectory + + vid_menucmdfn = VID_Menu_f; //johnfitz + vid_menudrawfn = VID_MenuDraw; + vid_menukeyfn = VID_MenuKey; + + VID_Gamma_Init(); //johnfitz + VID_Menu_Init(); //johnfitz + + //QuakeSpasm: current vid settings should override config file settings. + //so we have to lock the vid mode from now until after all config files are read. + vid_locked = true; +} + +// new proc by S.A., called by alt-return key binding. +void VID_Toggle (void) +{ + // disabling the fast path completely because SDL_SetWindowFullscreen was changing + // the window size on SDL2/WinXP and we weren't set up to handle it. --ericw + // + // TODO: Clear out the dead code, reinstate the fast path using SDL_SetWindowFullscreen + // inside VID_SetMode, check window size to fix WinXP issue. This will + // keep all the mode changing code in one place. + static qboolean vid_toggle_works = false; + qboolean toggleWorked; + Uint32 flags = 0; + + S_ClearBuffer (); + + if (!vid_toggle_works) + goto vrestart; + else if (gl_vbo_able) + { + // disabling the fast path because with SDL 1.2 it invalidates VBOs (using them + // causes a crash, sugesting that the fullscreen toggle created a new GL context, + // although texture objects remain valid for some reason). + // + // SDL2 does promise window resizes / fullscreen changes preserve the GL context, + // so we could use the fast path with SDL2. --ericw + vid_toggle_works = false; + goto vrestart; + } + + if (!VID_GetFullscreen()) { + flags = vid_desktopfullscreen.value ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; + } + + toggleWorked = SDL_SetWindowFullscreen(draw_context, flags) == 0; + + if (toggleWorked) + { + Sbar_Changed (); // Sbar seems to need refreshing + + modestate = VID_GetFullscreen() ? MS_FULLSCREEN : MS_WINDOWED; + + VID_SyncCvars(); + + // update mouse grab + if (key_dest == key_console || key_dest == key_menu) + { + if (modestate == MS_WINDOWED) + IN_Deactivate(true); + else if (modestate == MS_FULLSCREEN) + IN_Activate(); + } + } + else + { + vid_toggle_works = false; + Con_DPrintf ("SDL_WM_ToggleFullScreen failed, attempting VID_Restart\n"); + vrestart: + Cvar_SetQuick (&vid_fullscreen, VID_GetFullscreen() ? "0" : "1"); + Cbuf_AddText ("vid_restart\n"); + } +} + +/* +================ +VID_SyncCvars -- johnfitz -- set vid cvars to match current video mode +================ +*/ +void VID_SyncCvars (void) +{ + if (draw_context) + { + if (!VID_GetDesktopFullscreen()) + { + Cvar_SetValueQuick (&vid_width, VID_GetCurrentWidth()); + Cvar_SetValueQuick (&vid_height, VID_GetCurrentHeight()); + } + Cvar_SetValueQuick (&vid_refreshrate, VID_GetCurrentRefreshRate()); + Cvar_SetValueQuick (&vid_bpp, VID_GetCurrentBPP()); + Cvar_SetQuick (&vid_fullscreen, VID_GetFullscreen() ? "1" : "0"); + // don't sync vid_desktopfullscreen, it's a user preference that + // should persist even if we are in windowed mode. + Cvar_SetQuick (&vid_vsync, VID_GetVSync() ? "1" : "0"); + } + + vid_changed = false; +} + +//========================================================================== +// +// NEW VIDEO MENU -- johnfitz +// +//========================================================================== + +enum { + VID_OPT_MODE, + VID_OPT_BPP, + VID_OPT_REFRESHRATE, + VID_OPT_FULLSCREEN, + VID_OPT_VSYNC, + VID_OPT_TEST, + VID_OPT_APPLY, + VIDEO_OPTIONS_ITEMS +}; + +static int video_options_cursor = 0; + +typedef struct { + int width,height; +} vid_menu_mode; + +//TODO: replace these fixed-length arrays with hunk_allocated buffers +static vid_menu_mode vid_menu_modes[MAX_MODE_LIST]; +static int vid_menu_nummodes = 0; + +static int vid_menu_bpps[MAX_BPPS_LIST]; +static int vid_menu_numbpps = 0; + +static int vid_menu_rates[MAX_RATES_LIST]; +static int vid_menu_numrates=0; + +/* +================ +VID_Menu_Init +================ +*/ +static void VID_Menu_Init (void) +{ + int i, j, h, w; + + for (i = 0; i < nummodes; i++) + { + w = modelist[i].width; + h = modelist[i].height; + + for (j = 0; j < vid_menu_nummodes; j++) + { + if (vid_menu_modes[j].width == w && + vid_menu_modes[j].height == h) + break; + } + + if (j == vid_menu_nummodes) + { + vid_menu_modes[j].width = w; + vid_menu_modes[j].height = h; + vid_menu_nummodes++; + } + } +} + +/* +================ +VID_Menu_RebuildBppList + +regenerates bpp list based on current vid_width and vid_height +================ +*/ +static void VID_Menu_RebuildBppList (void) +{ + int i, j, b; + + vid_menu_numbpps = 0; + + for (i = 0; i < nummodes; i++) + { + if (vid_menu_numbpps >= MAX_BPPS_LIST) + break; + + //bpp list is limited to bpps available with current width/height + if (modelist[i].width != vid_width.value || + modelist[i].height != vid_height.value) + continue; + + b = modelist[i].bpp; + + for (j = 0; j < vid_menu_numbpps; j++) + { + if (vid_menu_bpps[j] == b) + break; + } + + if (j == vid_menu_numbpps) + { + vid_menu_bpps[j] = b; + vid_menu_numbpps++; + } + } + + //if there are no valid fullscreen bpps for this width/height, just pick one + if (vid_menu_numbpps == 0) + { + Cvar_SetValueQuick (&vid_bpp, (float)modelist[0].bpp); + return; + } + + //if vid_bpp is not in the new list, change vid_bpp + for (i = 0; i < vid_menu_numbpps; i++) + if (vid_menu_bpps[i] == (int)(vid_bpp.value)) + break; + + if (i == vid_menu_numbpps) + Cvar_SetValueQuick (&vid_bpp, (float)vid_menu_bpps[0]); +} + +/* +================ +VID_Menu_RebuildRateList + +regenerates rate list based on current vid_width, vid_height and vid_bpp +================ +*/ +static void VID_Menu_RebuildRateList (void) +{ + int i,j,r; + + vid_menu_numrates=0; + + for (i=0;i= vid_menu_nummodes) + i = 0; + else if (i < 0) + i = vid_menu_nummodes-1; + } + + Cvar_SetValueQuick (&vid_width, (float)vid_menu_modes[i].width); + Cvar_SetValueQuick (&vid_height, (float)vid_menu_modes[i].height); + VID_Menu_RebuildBppList (); + VID_Menu_RebuildRateList (); + } +} + +/* +================ +VID_Menu_ChooseNextBpp + +chooses next bpp in order, then updates vid_bpp cvar +================ +*/ +static void VID_Menu_ChooseNextBpp (int dir) +{ + int i; + + if (vid_menu_numbpps) + { + for (i = 0; i < vid_menu_numbpps; i++) + { + if (vid_menu_bpps[i] == vid_bpp.value) + break; + } + + if (i == vid_menu_numbpps) //can't find it in list + { + i = 0; + } + else + { + i += dir; + if (i >= vid_menu_numbpps) + i = 0; + else if (i < 0) + i = vid_menu_numbpps-1; + } + + Cvar_SetValueQuick (&vid_bpp, (float)vid_menu_bpps[i]); + } +} + +/* +================ +VID_Menu_ChooseNextRate + +chooses next refresh rate in order, then updates vid_refreshrate cvar +================ +*/ +static void VID_Menu_ChooseNextRate (int dir) +{ + int i; + + for (i=0;i=vid_menu_numrates) + i = 0; + else if (i<0) + i = vid_menu_numrates-1; + } + + Cvar_SetValue ("vid_refreshrate",(float)vid_menu_rates[i]); +} + +/* +================ +VID_MenuKey +================ +*/ +static void VID_MenuKey (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + VID_SyncCvars (); //sync cvars before leaving menu. FIXME: there are other ways to leave menu + S_LocalSound ("misc/menu1.wav"); + M_Menu_Options_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + video_options_cursor--; + if (video_options_cursor < 0) + video_options_cursor = VIDEO_OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + video_options_cursor++; + if (video_options_cursor >= VIDEO_OPTIONS_ITEMS) + video_options_cursor = 0; + break; + + case K_LEFTARROW: + S_LocalSound ("misc/menu3.wav"); + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); // kristian + break; + default: + break; + } + break; + + case K_RIGHTARROW: + S_LocalSound ("misc/menu3.wav"); + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (-1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (-1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (-1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); + break; + default: + break; + } + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); + break; + case VID_OPT_TEST: + Cbuf_AddText ("vid_test\n"); + break; + case VID_OPT_APPLY: + Cbuf_AddText ("vid_restart\n"); + key_dest = key_game; + m_state = m_none; + IN_Activate(); + break; + default: + break; + } + break; + + default: + break; + } +} + +/* +================ +VID_MenuDraw +================ +*/ +static void VID_MenuDraw (void) +{ + int i, y; + qpic_t *p; + const char *title; + + y = 4; + + // plaque + p = Draw_CachePic ("gfx/qplaque.lmp"); + M_DrawTransPic (16, y, p); + + //p = Draw_CachePic ("gfx/vidmodes.lmp"); + p = Draw_CachePic ("gfx/p_option.lmp"); + M_DrawPic ( (320-p->width)/2, y, p); + + y += 28; + + // title + title = "Video Options"; + M_PrintWhite ((320-8*strlen(title))/2, y, title); + + y += 16; + + // options + for (i = 0; i < VIDEO_OPTIONS_ITEMS; i++) + { + switch (i) + { + case VID_OPT_MODE: + M_Print (16, y, " Video mode"); + M_Print (184, y, va("%ix%i", (int)vid_width.value, (int)vid_height.value)); + break; + case VID_OPT_BPP: + M_Print (16, y, " Color depth"); + M_Print (184, y, va("%i", (int)vid_bpp.value)); + break; + case VID_OPT_REFRESHRATE: + M_Print (16, y, " Refresh rate"); + M_Print (184, y, va("%i", (int)vid_refreshrate.value)); + break; + case VID_OPT_FULLSCREEN: + M_Print (16, y, " Fullscreen"); + M_DrawCheckbox (184, y, (int)vid_fullscreen.value); + break; + case VID_OPT_VSYNC: + M_Print (16, y, " Vertical sync"); + if (gl_swap_control) + M_DrawCheckbox (184, y, (int)vid_vsync.value); + else + M_Print (184, y, "N/A"); + break; + case VID_OPT_TEST: + y += 8; //separate the test and apply items + M_Print (16, y, " Test changes"); + break; + case VID_OPT_APPLY: + M_Print (16, y, " Apply changes"); + break; + } + + if (video_options_cursor == i) + M_DrawCharacter (168, y, 12+((int)(realtime*4)&1)); + + y += 8; + } +} + +/* +================ +VID_Menu_f +================ +*/ +static void VID_Menu_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_video; + m_entersound = true; + + //set all the cvars to match the current mode when entering the menu + VID_SyncCvars (); + + //set up bpp and rate lists based on current cvars + VID_Menu_RebuildBppList (); + VID_Menu_RebuildRateList (); +} + diff --git a/source/gl_warp.c b/source/gl_warp.c new file mode 100644 index 0000000..0214531 --- /dev/null +++ b/source/gl_warp.c @@ -0,0 +1,272 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. +*/ +//gl_warp.c -- warping animation support + +#include "quakedef.h" + +extern cvar_t r_drawflat; + +cvar_t r_oldwater = {"r_oldwater", "0", CVAR_ARCHIVE}; +cvar_t r_waterquality = {"r_waterquality", "8", CVAR_NONE}; +cvar_t r_waterwarp = {"r_waterwarp", "1", CVAR_NONE}; + +int gl_warpimagesize; +float load_subdivide_size; //johnfitz -- remember what subdivide_size value was when this map was loaded + +float turbsin[] = +{ +#include "gl_warp_sin.h" +}; + +#define WARPCALC(s,t) ((s + turbsin[(int)((t*2)+(cl.time*(128.0/M_PI))) & 255]) * (1.0/64)) //johnfitz -- correct warp +#define WARPCALC2(s,t) ((s + turbsin[(int)((t*0.125+cl.time)*(128.0/M_PI)) & 255]) * (1.0/64)) //johnfitz -- old warp + +//============================================================================== +// +// OLD-STYLE WATER +// +//============================================================================== + +extern qmodel_t *loadmodel; + +msurface_t *warpface; + +cvar_t gl_subdivide_size = {"gl_subdivide_size", "128", CVAR_ARCHIVE}; + +void BoundPoly (int numverts, float *verts, vec3_t mins, vec3_t maxs) +{ + int i, j; + float *v; + + mins[0] = mins[1] = mins[2] = 999999999; + maxs[0] = maxs[1] = maxs[2] = -999999999; + v = verts; + for (i=0 ; i maxs[j]) + maxs[j] = *v; + } +} + +void SubdividePolygon (int numverts, float *verts) +{ + int i, j, k; + vec3_t mins, maxs; + float m; + float *v; + vec3_t front[64], back[64]; + int f, b; + float dist[64]; + float frac; + glpoly_t *poly; + float s, t; + + if (numverts > 60) + Sys_Error ("numverts = %i", numverts); + + BoundPoly (numverts, verts, mins, maxs); + + for (i=0 ; i<3 ; i++) + { + m = (mins[i] + maxs[i]) * 0.5; + m = gl_subdivide_size.value * floor (m/gl_subdivide_size.value + 0.5); + if (maxs[i] - m < 8) + continue; + if (m - mins[i] < 8) + continue; + + // cut it + v = verts + i; + for (j=0 ; j= 0) + { + VectorCopy (v, front[f]); + f++; + } + if (dist[j] <= 0) + { + VectorCopy (v, back[b]); + b++; + } + if (dist[j] == 0 || dist[j+1] == 0) + continue; + if ( (dist[j] > 0) != (dist[j+1] > 0) ) + { + // clip point + frac = dist[j] / (dist[j] - dist[j+1]); + for (k=0 ; k<3 ; k++) + front[f][k] = back[b][k] = v[k] + frac*(v[3+k] - v[k]); + f++; + b++; + } + } + + SubdividePolygon (f, front[0]); + SubdividePolygon (b, back[0]); + return; + } + + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = warpface->polys->next; + warpface->polys->next = poly; + poly->numverts = numverts; + for (i=0 ; iverts[i]); + s = DotProduct (verts, warpface->texinfo->vecs[0]); + t = DotProduct (verts, warpface->texinfo->vecs[1]); + poly->verts[i][3] = s; + poly->verts[i][4] = t; + } +} + +/* +================ +GL_SubdivideSurface +================ +*/ +void GL_SubdivideSurface (msurface_t *fa) +{ + vec3_t verts[64]; + int i; + + warpface = fa; + + //the first poly in the chain is the undivided poly for newwater rendering. + //grab the verts from that. + for (i=0; ipolys->numverts; i++) + VectorCopy (fa->polys->verts[i], verts[i]); + + SubdividePolygon (fa->polys->numverts, verts[0]); +} + +/* +================ +DrawWaterPoly -- johnfitz +================ +*/ +void DrawWaterPoly (glpoly_t *p) +{ + float *v; + int i; + + if (load_subdivide_size > 48) + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (WARPCALC2(v[3],v[4]), WARPCALC2(v[4],v[3])); + glVertex3fv (v); + } + glEnd (); + } + else + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (WARPCALC(v[3],v[4]), WARPCALC(v[4],v[3])); + glVertex3fv (v); + } + glEnd (); + } +} + +//============================================================================== +// +// RENDER-TO-FRAMEBUFFER WATER +// +//============================================================================== + +/* +============= +R_UpdateWarpTextures -- johnfitz -- each frame, update warping textures +============= +*/ +void R_UpdateWarpTextures (void) +{ + texture_t *tx; + int i; + float x, y, x2, warptess; + + if (r_oldwater.value || cl.paused || r_drawflat_cheatsafe || r_lightmap_cheatsafe) + return; + + warptess = 128.0/CLAMP (3.0, floor(r_waterquality.value), 64.0); + + for (i=0; inumtextures; i++) + { + if (!(tx = cl.worldmodel->textures[i])) + continue; + + if (!tx->update_warp) + continue; + + //render warp + GL_SetCanvas (CANVAS_WARPIMAGE); + GL_Bind (tx->gltexture); + for (x=0.0; x<128.0; x=x2) + { + x2 = x + warptess; + glBegin (GL_TRIANGLE_STRIP); + for (y=0.0; y<128.01; y+=warptess) // .01 for rounding errors + { + glTexCoord2f (WARPCALC(x,y), WARPCALC(y,x)); + glVertex2f (x,y); + glTexCoord2f (WARPCALC(x2,y), WARPCALC(y,x2)); + glVertex2f (x2,y); + } + glEnd(); + } + + //copy to texture + GL_Bind (tx->warpimage); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, glx, gly+glheight-gl_warpimagesize, gl_warpimagesize, gl_warpimagesize); + + tx->update_warp = false; + } + + // ericw -- workaround for osx 10.6 driver bug when using FSAA. R_Clear only clears the warpimage part of the screen. + GL_SetCanvas(CANVAS_DEFAULT); + + //if warp render went down into sbar territory, we need to be sure to refresh it next frame + if (gl_warpimagesize + sb_lines > glheight) + Sbar_Changed (); + + //if viewsize is less than 100, we need to redraw the frame around the viewport + scr_tileclear_updates = 0; +} diff --git a/source/gl_warp_sin.h b/source/gl_warp_sin.h new file mode 100644 index 0000000..313ab4e --- /dev/null +++ b/source/gl_warp_sin.h @@ -0,0 +1,84 @@ +/* + * gl_warp_sin.h + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + 0, 0.19633, 0.392541, 0.588517, + 0.784137, 0.979285, 1.17384, 1.3677, + 1.56072, 1.75281, 1.94384, 2.1337, + 2.32228, 2.50945, 2.69512, 2.87916, + 3.06147, 3.24193, 3.42044, 3.59689, + 3.77117, 3.94319, 4.11282, 4.27998, + 4.44456, 4.60647, 4.76559, 4.92185, + 5.07515, 5.22538, 5.37247, 5.51632, + 5.65685, 5.79398, 5.92761, 6.05767, + 6.18408, 6.30677, 6.42566, 6.54068, + 6.65176, 6.75883, 6.86183, 6.9607, + 7.05537, 7.14579, 7.23191, 7.31368, + 7.39104, 7.46394, 7.53235, 7.59623, + 7.65552, 7.71021, 7.76025, 7.80562, + 7.84628, 7.88222, 7.91341, 7.93984, + 7.96148, 7.97832, 7.99036, 7.99759, + 8, 7.99759, 7.99036, 7.97832, + 7.96148, 7.93984, 7.91341, 7.88222, + 7.84628, 7.80562, 7.76025, 7.71021, + 7.65552, 7.59623, 7.53235, 7.46394, + 7.39104, 7.31368, 7.23191, 7.14579, + 7.05537, 6.9607, 6.86183, 6.75883, + 6.65176, 6.54068, 6.42566, 6.30677, + 6.18408, 6.05767, 5.92761, 5.79398, + 5.65685, 5.51632, 5.37247, 5.22538, + 5.07515, 4.92185, 4.76559, 4.60647, + 4.44456, 4.27998, 4.11282, 3.94319, + 3.77117, 3.59689, 3.42044, 3.24193, + 3.06147, 2.87916, 2.69512, 2.50945, + 2.32228, 2.1337, 1.94384, 1.75281, + 1.56072, 1.3677, 1.17384, 0.979285, + 0.784137, 0.588517, 0.392541, 0.19633, + 9.79717e-16, -0.19633, -0.392541, -0.588517, + -0.784137, -0.979285, -1.17384, -1.3677, + -1.56072, -1.75281, -1.94384, -2.1337, + -2.32228, -2.50945, -2.69512, -2.87916, + -3.06147, -3.24193, -3.42044, -3.59689, + -3.77117, -3.94319, -4.11282, -4.27998, + -4.44456, -4.60647, -4.76559, -4.92185, + -5.07515, -5.22538, -5.37247, -5.51632, + -5.65685, -5.79398, -5.92761, -6.05767, + -6.18408, -6.30677, -6.42566, -6.54068, + -6.65176, -6.75883, -6.86183, -6.9607, + -7.05537, -7.14579, -7.23191, -7.31368, + -7.39104, -7.46394, -7.53235, -7.59623, + -7.65552, -7.71021, -7.76025, -7.80562, + -7.84628, -7.88222, -7.91341, -7.93984, + -7.96148, -7.97832, -7.99036, -7.99759, + -8, -7.99759, -7.99036, -7.97832, + -7.96148, -7.93984, -7.91341, -7.88222, + -7.84628, -7.80562, -7.76025, -7.71021, + -7.65552, -7.59623, -7.53235, -7.46394, + -7.39104, -7.31368, -7.23191, -7.14579, + -7.05537, -6.9607, -6.86183, -6.75883, + -6.65176, -6.54068, -6.42566, -6.30677, + -6.18408, -6.05767, -5.92761, -5.79398, + -5.65685, -5.51632, -5.37247, -5.22538, + -5.07515, -4.92185, -4.76559, -4.60647, + -4.44456, -4.27998, -4.11282, -3.94319, + -3.77117, -3.59689, -3.42044, -3.24193, + -3.06147, -2.87916, -2.69512, -2.50945, + -2.32228, -2.1337, -1.94384, -1.75281, + -1.56072, -1.3677, -1.17384, -0.979285, + -0.784137, -0.588517, -0.392541, -0.19633, + diff --git a/source/glquake.h b/source/glquake.h new file mode 100644 index 0000000..e9d250e --- /dev/null +++ b/source/glquake.h @@ -0,0 +1,405 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 + +void GL_BeginRendering (int *x, int *y, int *width, int *height); +void GL_EndRendering (void); +void GL_Set2D (void); + +extern int glx, gly, glwidth, glheight; + +#define GL_UNUSED_TEXTURE (~(GLuint)0) + +// r_local.h -- private refresh defs + +#define ALIAS_BASE_SIZE_RATIO (1.0 / 11.0) + // normalizing factor so player model works out to about + // 1 pixel per triangle +#define MAX_LBM_HEIGHT 480 + +#define TILE_SIZE 128 // size of textures generated by R_GenTiledSurf + +#define SKYSHIFT 7 +#define SKYSIZE (1 << SKYSHIFT) +#define SKYMASK (SKYSIZE - 1) + +#define BACKFACE_EPSILON 0.01 + + +void R_TimeRefresh_f (void); +void R_ReadPointFile_f (void); +texture_t *R_TextureAnimation (texture_t *base, int frame); + +typedef struct surfcache_s +{ + struct surfcache_s *next; + struct surfcache_s **owner; // NULL is an empty chunk of memory + int lightadj[MAXLIGHTMAPS]; // checked for strobe flush + int dlight; + int size; // including header + unsigned width; + unsigned height; // DEBUG only needed for debug + float mipscale; + struct texture_s *texture; // checked for animating textures + byte data[4]; // width*height elements +} surfcache_t; + + +typedef struct +{ + pixel_t *surfdat; // destination for generated surface + int rowbytes; // destination logical width in bytes + msurface_t *surf; // description for surface to generate + fixed8_t lightadj[MAXLIGHTMAPS]; + // adjust for lightmap levels for dynamic lighting + texture_t *texture; // corrected for animating textures + int surfmip; // mipmapped ratio of surface texels / world pixels + int surfwidth; // in mipmapped texels + int surfheight; // in mipmapped texels +} drawsurf_t; + + +typedef enum { + pt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2 +} ptype_t; + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +typedef struct particle_s +{ +// driver-usable fields + vec3_t org; + float color; +// drivers never touch the following fields + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; +} particle_t; + + +//==================================================== + +extern qboolean r_cache_thrash; // compatability +extern vec3_t modelorg, r_entorigin; +extern entity_t *currententity; +extern int r_visframecount; // ??? what difs? +extern int r_framecount; +extern mplane_t frustum[4]; + +// +// view origin +// +extern vec3_t vup; +extern vec3_t vpn; +extern vec3_t vright; +extern vec3_t r_origin; + +// +// screen size info +// +extern refdef_t r_refdef; +extern mleaf_t *r_viewleaf, *r_oldviewleaf; +extern int d_lightstylevalue[256]; // 8.8 fraction of base light value + +extern cvar_t r_norefresh; +extern cvar_t r_drawentities; +extern cvar_t r_drawworld; +extern cvar_t r_drawviewmodel; +extern cvar_t r_speeds; +extern cvar_t r_pos; +extern cvar_t r_waterwarp; +extern cvar_t r_fullbright; +extern cvar_t r_lightmap; +extern cvar_t r_shadows; +extern cvar_t r_wateralpha; +extern cvar_t r_lavaalpha; +extern cvar_t r_telealpha; +extern cvar_t r_slimealpha; +extern cvar_t r_dynamic; +extern cvar_t r_novis; +extern cvar_t r_scale; + +extern cvar_t gl_clear; +extern cvar_t gl_cull; +extern cvar_t gl_smoothmodels; +extern cvar_t gl_affinemodels; +extern cvar_t gl_polyblend; +extern cvar_t gl_flashblend; +extern cvar_t gl_nocolors; + +extern cvar_t gl_playermip; + +extern cvar_t gl_subdivide_size; +extern float load_subdivide_size; //johnfitz -- remember what subdivide_size value was when this map was loaded + +extern int gl_stencilbits; + +// Multitexture +extern qboolean mtexenabled; +extern qboolean gl_mtexable; +extern PFNGLMULTITEXCOORD2FARBPROC GL_MTexCoord2fFunc; +extern PFNGLACTIVETEXTUREARBPROC GL_SelectTextureFunc; +extern PFNGLCLIENTACTIVETEXTUREARBPROC GL_ClientActiveTextureFunc; +extern GLint gl_max_texture_units; //ericw + +//johnfitz -- anisotropic filtering +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +extern float gl_max_anisotropy; +extern qboolean gl_anisotropy_able; + +//ericw -- VBO +extern PFNGLBINDBUFFERARBPROC GL_BindBufferFunc; +extern PFNGLBUFFERDATAARBPROC GL_BufferDataFunc; +extern PFNGLBUFFERSUBDATAARBPROC GL_BufferSubDataFunc; +extern PFNGLDELETEBUFFERSARBPROC GL_DeleteBuffersFunc; +extern PFNGLGENBUFFERSARBPROC GL_GenBuffersFunc; +extern qboolean gl_vbo_able; +//ericw + +//ericw -- GLSL + +// SDL 1.2 has a bug where it doesn't provide these typedefs on OS X! +typedef GLuint (APIENTRYP QS_PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP QS_PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP QS_PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLuint (APIENTRYP QS_PFNGLCREATEPROGRAMPROC) (void); +typedef void (APIENTRYP QS_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP QS_PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLBINDATTRIBLOCATIONFUNC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUSEPROGRAMPROC) (GLuint program); +typedef GLint (APIENTRYP QS_PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP QS_PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP QS_PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + +extern QS_PFNGLCREATESHADERPROC GL_CreateShaderFunc; +extern QS_PFNGLDELETESHADERPROC GL_DeleteShaderFunc; +extern QS_PFNGLDELETEPROGRAMPROC GL_DeleteProgramFunc; +extern QS_PFNGLSHADERSOURCEPROC GL_ShaderSourceFunc; +extern QS_PFNGLCOMPILESHADERPROC GL_CompileShaderFunc; +extern QS_PFNGLGETSHADERIVPROC GL_GetShaderivFunc; +extern QS_PFNGLGETSHADERINFOLOGPROC GL_GetShaderInfoLogFunc; +extern QS_PFNGLGETPROGRAMIVPROC GL_GetProgramivFunc; +extern QS_PFNGLGETPROGRAMINFOLOGPROC GL_GetProgramInfoLogFunc; +extern QS_PFNGLCREATEPROGRAMPROC GL_CreateProgramFunc; +extern QS_PFNGLATTACHSHADERPROC GL_AttachShaderFunc; +extern QS_PFNGLLINKPROGRAMPROC GL_LinkProgramFunc; +extern QS_PFNGLBINDATTRIBLOCATIONFUNC GL_BindAttribLocationFunc; +extern QS_PFNGLUSEPROGRAMPROC GL_UseProgramFunc; +extern QS_PFNGLGETATTRIBLOCATIONPROC GL_GetAttribLocationFunc; +extern QS_PFNGLVERTEXATTRIBPOINTERPROC GL_VertexAttribPointerFunc; +extern QS_PFNGLENABLEVERTEXATTRIBARRAYPROC GL_EnableVertexAttribArrayFunc; +extern QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC GL_DisableVertexAttribArrayFunc; +extern QS_PFNGLGETUNIFORMLOCATIONPROC GL_GetUniformLocationFunc; +extern QS_PFNGLUNIFORM1IPROC GL_Uniform1iFunc; +extern QS_PFNGLUNIFORM1FPROC GL_Uniform1fFunc; +extern QS_PFNGLUNIFORM3FPROC GL_Uniform3fFunc; +extern QS_PFNGLUNIFORM4FPROC GL_Uniform4fFunc; +extern qboolean gl_glsl_able; +extern qboolean gl_glsl_gamma_able; +extern qboolean gl_glsl_alias_able; +// ericw -- + +//ericw -- NPOT texture support +extern qboolean gl_texture_NPOT; + +//johnfitz -- polygon offset +#define OFFSET_BMODEL 1 +#define OFFSET_NONE 0 +#define OFFSET_DECAL -1 +#define OFFSET_FOG -2 +#define OFFSET_SHOWTRIS -3 +void GL_PolygonOffset (int); + +//johnfitz -- GL_EXT_texture_env_combine +//the values for GL_ARB_ are identical +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +extern qboolean gl_texture_env_combine; +extern qboolean gl_texture_env_add; // for GL_EXT_texture_env_add + +//johnfitz -- rendering statistics +extern int rs_brushpolys, rs_aliaspolys, rs_skypolys, rs_particles, rs_fogpolys; +extern int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses; +extern float rs_megatexels; + +//johnfitz -- track developer statistics that vary every frame +extern cvar_t devstats; +typedef struct { + int packetsize; + int edicts; + int visedicts; + int efrags; + int tempents; + int beams; + int dlights; +} devstats_t; +extern devstats_t dev_stats, dev_peakstats; + +//ohnfitz -- reduce overflow warning spam +typedef struct { + double packetsize; + double efrags; + double beams; + double varstring; +} overflowtimes_t; +extern overflowtimes_t dev_overflows; //this stores the last time overflow messages were displayed, not the last time overflows occured +#define CONSOLE_RESPAM_TIME 3 // seconds between repeated warning messages + +//johnfitz -- moved here from r_brush.c +extern int gl_lightmap_format, lightmap_bytes; +#define MAX_LIGHTMAPS 512 //johnfitz -- was 64 +extern gltexture_t *lightmap_textures[MAX_LIGHTMAPS]; //johnfitz -- changed to an array + +extern int gl_warpimagesize; //johnfitz -- for water warp + +extern qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz + +typedef struct glsl_attrib_binding_s { + const char *name; + GLuint attrib; +} glsl_attrib_binding_t; + +extern float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; //ericw + +//johnfitz -- fog functions called from outside gl_fog.c +void Fog_ParseServerMessage (void); +float *Fog_GetColor (void); +float Fog_GetDensity (void); +void Fog_EnableGFog (void); +void Fog_DisableGFog (void); +void Fog_StartAdditive (void); +void Fog_StopAdditive (void); +void Fog_SetupFrame (void); +void Fog_NewMap (void); +void Fog_Init (void); +void Fog_SetupState (void); + +void R_NewGame (void); + +void R_AnimateLight (void); +void R_MarkSurfaces (void); +void R_CullSurfaces (void); +qboolean R_CullBox (vec3_t emins, vec3_t emaxs); +void R_StoreEfrags (efrag_t **ppefrag); +qboolean R_CullModelForEntity (entity_t *e); +void R_RotateForEntity (vec3_t origin, vec3_t angles); +void R_MarkLights (dlight_t *light, int num, mnode_t *node); + +void R_InitParticles (void); +void R_DrawParticles (void); +void CL_RunParticles (void); +void R_ClearParticles (void); + +void R_TranslatePlayerSkin (int playernum); +void R_TranslateNewPlayerSkin (int playernum); //johnfitz -- this handles cases when the actual texture changes +void R_UpdateWarpTextures (void); + +void R_DrawWorld (void); +void R_DrawAliasModel (entity_t *e); +void R_DrawBrushModel (entity_t *e); +void R_DrawSpriteModel (entity_t *e); + +void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain); + +void R_RenderDlights (void); +void GL_BuildLightmaps (void); +void GL_DeleteBModelVertexBuffer (void); +void GL_BuildBModelVertexBuffer (void); +void GLMesh_LoadVertexBuffers (void); +void GLMesh_DeleteVertexBuffers (void); +void R_RebuildAllLightmaps (void); + +int R_LightPoint (vec3_t p); + +void GL_SubdivideSurface (msurface_t *fa); +void R_BuildLightMap (msurface_t *surf, byte *dest, int stride); +void R_RenderDynamicLightmaps (msurface_t *fa); +void R_UploadLightmaps (void); + +void R_DrawWorld_ShowTris (void); +void R_DrawBrushModel_ShowTris (entity_t *e); +void R_DrawAliasModel_ShowTris (entity_t *e); +void R_DrawParticles_ShowTris (void); + +GLint GL_GetUniformLocation (GLuint *programPtr, const char *name); +GLuint GL_CreateProgram (const GLchar *vertSource, const GLchar *fragSource, int numbindings, const glsl_attrib_binding_t *bindings); +void R_DeleteShaders (void); + +void GLWorld_CreateShaders (void); +void GLAlias_CreateShaders (void); +void GL_DrawAliasShadow (entity_t *e); +void DrawGLTriangleFan (glpoly_t *p); +void DrawGLPoly (glpoly_t *p); +void DrawWaterPoly (glpoly_t *p); +void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr); + +void Sky_Init (void); +void Sky_DrawSky (void); +void Sky_NewMap (void); +void Sky_LoadTexture (texture_t *mt); +void Sky_LoadSkyBox (const char *name); + +void TexMgr_RecalcWarpImageSize (void); + +void R_ClearTextureChains (qmodel_t *mod, texchain_t chain); +void R_ChainSurface (msurface_t *surf, texchain_t chain); +void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain); +void R_DrawWorld_Water (void); + +void GL_BindBuffer (GLenum target, GLuint buffer); +void GL_ClearBufferBindings (); + +void GLSLGamma_DeleteTexture (void); +void GLSLGamma_GammaCorrect (void); + +void R_ScaleView_DeleteTexture (void); + +float GL_WaterAlphaForSurface (msurface_t *fa); + +#endif /* __GLQUAKE_H */ + diff --git a/source/host.c b/source/host.c new file mode 100644 index 0000000..ad60160 --- /dev/null +++ b/source/host.c @@ -0,0 +1,935 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "bgmusic.h" +#include + +/* + +A server can allways 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. + +*/ + +quakeparms_t *host_parms; + +qboolean host_initialized; // true if into command execution + +double host_frametime; +double realtime; // without any filtering or bounding +double oldrealtime; // last frame run + +int host_framecount; + +int host_hunklevel; + +int minimum_memory; + +client_t *host_client; // current client + +jmp_buf host_abortserver; + +byte *host_colormap; + +cvar_t host_framerate = {"host_framerate","0",CVAR_NONE}; // set for slow motion +cvar_t host_speeds = {"host_speeds","0",CVAR_NONE}; // set for running times +cvar_t host_maxfps = {"host_maxfps", "72", CVAR_ARCHIVE}; //johnfitz +cvar_t host_timescale = {"host_timescale", "0", CVAR_NONE}; //johnfitz +cvar_t max_edicts = {"max_edicts", "8192", CVAR_NONE}; //johnfitz //ericw -- changed from 2048 to 8192, removed CVAR_ARCHIVE + +cvar_t sys_ticrate = {"sys_ticrate","0.05",CVAR_NONE}; // dedicated server +cvar_t serverprofile = {"serverprofile","0",CVAR_NONE}; + +cvar_t fraglimit = {"fraglimit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t timelimit = {"timelimit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t teamplay = {"teamplay","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t samelevel = {"samelevel","0",CVAR_NONE}; +cvar_t noexit = {"noexit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t skill = {"skill","1",CVAR_NONE}; // 0 - 3 +cvar_t deathmatch = {"deathmatch","0",CVAR_NONE}; // 0, 1, or 2 +cvar_t coop = {"coop","0",CVAR_NONE}; // 0 or 1 + +cvar_t pausable = {"pausable","1",CVAR_NONE}; + +cvar_t developer = {"developer","0",CVAR_NONE}; + +cvar_t temp1 = {"temp1","0",CVAR_NONE}; + +cvar_t devstats = {"devstats","0",CVAR_NONE}; //johnfitz -- track developer statistics that vary every frame + +devstats_t dev_stats, dev_peakstats; +overflowtimes_t dev_overflows; //this stores the last time overflow messages were displayed, not the last time overflows occured + +/* +================ +Max_Edicts_f -- johnfitz +================ +*/ +static void Max_Edicts_f (cvar_t *var) +{ + //TODO: clamp it here? + if (cls.state == ca_connected || sv.active) + Con_Printf ("Changes to max_edicts will not take effect until the next time a map is loaded.\n"); +} + +/* +================ +Max_Fps_f -- ericw +================ +*/ +static void Max_Fps_f (cvar_t *var) +{ + if (var->value > 72) + Con_Warning ("host_maxfps above 72 breaks physics.\n"); +} + +/* +================ +Host_EndGame +================ +*/ +void Host_EndGame (const char *message, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,message); + q_vsnprintf (string, sizeof(string), message, argptr); + va_end (argptr); + Con_DPrintf ("Host_EndGame: %s\n",string); + + if (sv.active) + Host_ShutdownServer (false); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_EndGame: %s\n",string); // dedicated servers exit + + if (cls.demonum != -1) + CL_NextDemo (); + else + CL_Disconnect (); + + longjmp (host_abortserver, 1); +} + +/* +================ +Host_Error + +This shuts down both the client and server +================ +*/ +void Host_Error (const char *error, ...) +{ + va_list argptr; + char string[1024]; + static qboolean inerror = false; + + if (inerror) + Sys_Error ("Host_Error: recursively entered"); + inerror = true; + + SCR_EndLoadingPlaque (); // reenable screen updates + + va_start (argptr,error); + q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + Con_Printf ("Host_Error: %s\n",string); + + if (sv.active) + Host_ShutdownServer (false); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_Error: %s\n",string); // dedicated servers exit + + CL_Disconnect (); + cls.demonum = -1; + cl.intermission = 0; //johnfitz -- for errors during intermissions (changelevel with no map found, etc.) + + inerror = false; + + longjmp (host_abortserver, 1); +} + +/* +================ +Host_FindMaxClients +================ +*/ +void Host_FindMaxClients (void) +{ + int i; + + svs.maxclients = 1; + + i = COM_CheckParm ("-dedicated"); + if (i) + { + cls.state = ca_dedicated; + if (i != (com_argc - 1)) + { + svs.maxclients = Q_atoi (com_argv[i+1]); + } + else + svs.maxclients = 8; + } + else + cls.state = ca_disconnected; + + i = COM_CheckParm ("-listen"); + if (i) + { + if (cls.state == ca_dedicated) + Sys_Error ("Only one of -dedicated or -listen can be specified"); + if (i != (com_argc - 1)) + svs.maxclients = Q_atoi (com_argv[i+1]); + else + svs.maxclients = 8; + } + if (svs.maxclients < 1) + svs.maxclients = 8; + else if (svs.maxclients > MAX_SCOREBOARD) + svs.maxclients = MAX_SCOREBOARD; + + svs.maxclientslimit = svs.maxclients; + if (svs.maxclientslimit < 4) + svs.maxclientslimit = 4; + svs.clients = (struct client_s *) Hunk_AllocName (svs.maxclientslimit*sizeof(client_t), "clients"); + + if (svs.maxclients > 1) + Cvar_SetQuick (&deathmatch, "1"); + else + Cvar_SetQuick (&deathmatch, "0"); +} + +void Host_Version_f (void) +{ + Con_Printf ("Quake Version %1.2f\n", VERSION); + Con_Printf ("QuakeSpasm Version " QUAKESPASM_VER_STRING "\n"); + Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n"); +} + +/* cvar callback functions : */ +void Host_Callback_Notify (cvar_t *var) +{ + if (sv.active) + SV_BroadcastPrintf ("\"%s\" changed to \"%s\"\n", var->name, var->string); +} + +/* +======================= +Host_InitLocal +====================== +*/ +void Host_InitLocal (void) +{ + Cmd_AddCommand ("version", Host_Version_f); + + Host_InitCommands (); + + Cvar_RegisterVariable (&host_framerate); + Cvar_RegisterVariable (&host_speeds); + Cvar_RegisterVariable (&host_maxfps); //johnfitz + Cvar_SetCallback (&host_maxfps, Max_Fps_f); + Cvar_RegisterVariable (&host_timescale); //johnfitz + + Cvar_RegisterVariable (&max_edicts); //johnfitz + Cvar_SetCallback (&max_edicts, Max_Edicts_f); + Cvar_RegisterVariable (&devstats); //johnfitz + + Cvar_RegisterVariable (&sys_ticrate); + Cvar_RegisterVariable (&sys_throttle); + Cvar_RegisterVariable (&serverprofile); + + Cvar_RegisterVariable (&fraglimit); + Cvar_RegisterVariable (&timelimit); + Cvar_RegisterVariable (&teamplay); + Cvar_SetCallback (&fraglimit, Host_Callback_Notify); + Cvar_SetCallback (&timelimit, Host_Callback_Notify); + Cvar_SetCallback (&teamplay, Host_Callback_Notify); + Cvar_RegisterVariable (&samelevel); + Cvar_RegisterVariable (&noexit); + Cvar_SetCallback (&noexit, Host_Callback_Notify); + Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&developer); + Cvar_RegisterVariable (&coop); + Cvar_RegisterVariable (&deathmatch); + + Cvar_RegisterVariable (&pausable); + + Cvar_RegisterVariable (&temp1); + + Host_FindMaxClients (); +} + + +/* +=============== +Host_WriteConfiguration + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +void Host_WriteConfiguration (void) +{ + FILE *f; + +// dedicated servers initialize the host but don't parse and set the +// config.cfg cvars + if (host_initialized && !isDedicated && !host_parms->errstate) + { + f = fopen (va("%s/config.cfg", com_gamedir), "w"); + if (!f) + { + Con_Printf ("Couldn't write config.cfg.\n"); + return; + } + + //VID_SyncCvars (); //johnfitz -- write actual current mode to config file, in case cvars were messed with + + Key_WriteBindings (f); + Cvar_WriteVariables (f); + + //johnfitz -- extra commands to preserve state + fprintf (f, "vid_restart\n"); + if (in_mlook.state & 1) fprintf (f, "+mlook\n"); + //johnfitz + + fclose (f); + } +} + + +/* +================= +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 string[1024]; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt,argptr); + va_end (argptr); + + MSG_WriteByte (&host_client->message, svc_print); + MSG_WriteString (&host_client->message, string); +} + +/* +================= +SV_BroadcastPrintf + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrintf (const char *fmt, ...) +{ + va_list argptr; + char string[1024]; + int i; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt, argptr); + va_end (argptr); + + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active && svs.clients[i].spawned) + { + MSG_WriteByte (&svs.clients[i].message, svc_print); + MSG_WriteString (&svs.clients[i].message, string); + } + } +} + +/* +================= +Host_ClientCommands + +Send text over to the client to be executed +================= +*/ +void Host_ClientCommands (const char *fmt, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt, argptr); + va_end (argptr); + + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->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 saveSelf; + int i; + client_t *client; + + if (!crash) + { + // send any final messages (don't check for errors) + if (NET_CanSendMessage (host_client->netconnection)) + { + MSG_WriteByte (&host_client->message, svc_disconnect); + NET_SendMessage (host_client->netconnection, &host_client->message); + } + + if (host_client->edict && host_client->spawned) + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + saveSelf = pr_global_struct->self; + pr_global_struct->self = EDICT_TO_PROG(host_client->edict); + PR_ExecuteProgram (pr_global_struct->ClientDisconnect); + pr_global_struct->self = saveSelf; + } + + Sys_Printf ("Client %s removed\n",host_client->name); + } + +// break the net connection + NET_Close (host_client->netconnection); + host_client->netconnection = NULL; + +// free the client (the body stays around) + host_client->active = false; + host_client->name[0] = 0; + host_client->old_frags = -999999; + net_activeconnections--; + +// send notification to all clients + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + if (!client->active) + continue; + MSG_WriteByte (&client->message, svc_updatename); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteString (&client->message, ""); + MSG_WriteByte (&client->message, svc_updatefrags); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteShort (&client->message, 0); + MSG_WriteByte (&client->message, svc_updatecolors); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteByte (&client->message, 0); + } +} + +/* +================== +Host_ShutdownServer + +This only happens at the end of a game, not between levels +================== +*/ +void Host_ShutdownServer(qboolean crash) +{ + int i; + int count; + sizebuf_t buf; + byte message[4]; + double start; + + if (!sv.active) + return; + + sv.active = false; + +// stop all client sounds immediately + if (cls.state == ca_connected) + CL_Disconnect (); + +// flush any pending messages - like the score!!! + start = Sys_DoubleTime(); + do + { + count = 0; + for (i=0, host_client = svs.clients ; iactive && host_client->message.cursize) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + NET_SendMessage(host_client->netconnection, &host_client->message); + SZ_Clear (&host_client->message); + } + else + { + NET_GetMessage(host_client->netconnection); + count++; + } + } + } + if ((Sys_DoubleTime() - start) > 3.0) + break; + } + while (count); + +// make sure all the clients know we're disconnecting + buf.data = message; + buf.maxsize = 4; + buf.cursize = 0; + MSG_WriteByte(&buf, svc_disconnect); + count = NET_SendToAll(&buf, 5.0); + if (count) + Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); + + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + if (host_client->active) + SV_DropClient(crash); + +// +// clear structures +// +// memset (&sv, 0, sizeof(sv)); // ServerSpawn already do this by Host_ClearMemory + memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t)); +} + + +/* +================ +Host_ClearMemory + +This clears all the memory used by both the client and server, but does +not reinitialize anything. +================ +*/ +void Host_ClearMemory (void) +{ + Con_DPrintf ("Clearing memory\n"); + D_FlushCaches (); + Mod_ClearAll (); +/* host_hunklevel MUST be set at this point */ + Hunk_FreeToLowMark (host_hunklevel); + cls.signon = 0; + free(sv.edicts); // ericw -- sv.edicts switched to use malloc() + memset (&sv, 0, sizeof(sv)); + memset (&cl, 0, sizeof(cl)); +} + + +//============================================================================== +// +// Host Frame +// +//============================================================================== + +/* +=================== +Host_FilterTime + +Returns false if the time is too short to run a frame +=================== +*/ +qboolean Host_FilterTime (float time) +{ + float maxfps; //johnfitz + + realtime += time; + + //johnfitz -- max fps cvar + maxfps = CLAMP (10.0, host_maxfps.value, 1000.0); + if (!cls.timedemo && realtime - oldrealtime < 1.0/maxfps) + return false; // framerate is too high + //johnfitz + + host_frametime = realtime - oldrealtime; + oldrealtime = realtime; + + //johnfitz -- host_timescale is more intuitive than host_framerate + if (host_timescale.value > 0) + host_frametime *= host_timescale.value; + //johnfitz + else if (host_framerate.value > 0) + host_frametime = host_framerate.value; + else // don't allow really long or short frames + host_frametime = CLAMP (0.001, host_frametime, 0.1); //johnfitz -- use CLAMP + + return true; +} + +/* +=================== +Host_GetConsoleCommands + +Add them exactly as if they had been typed at the console +=================== +*/ +void Host_GetConsoleCommands (void) +{ + const char *cmd; + + if (!isDedicated) + return; // no stdin necessary in graphical mode + + while (1) + { + cmd = Sys_ConsoleInput (); + if (!cmd) + break; + Cbuf_AddText (cmd); + } +} + +/* +================== +Host_ServerFrame +================== +*/ +void Host_ServerFrame (void) +{ + int i, active; //johnfitz + edict_t *ent; //johnfitz + +// run the world state + pr_global_struct->frametime = host_frametime; + +// set the time and clear the general datagram + SV_ClearDatagram (); + +// check for new clients + SV_CheckForNewClients (); + +// read client messages + SV_RunClients (); + +// move things around and think +// always pause in single player if in console or menus + if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) ) + SV_Physics (); + +//johnfitz -- devstats + if (cls.signon == SIGNONS) + { + for (i=0, active=0; ifree) + active++; + } + if (active > 600 && dev_peakstats.edicts <= 600) + Con_DWarning ("%i edicts exceeds standard limit of 600 (max = %d).\n", active, sv.max_edicts); + dev_stats.edicts = active; + dev_peakstats.edicts = q_max(active, dev_peakstats.edicts); + } +//johnfitz + +// send all messages to the clients + SV_SendClientMessages (); +} + +/* +================== +Host_Frame + +Runs all active servers +================== +*/ +void _Host_Frame (float time) +{ + static double time1 = 0; + static double time2 = 0; + static double time3 = 0; + int pass1, pass2, pass3; + + if (setjmp (host_abortserver) ) + return; // something bad happened, or the server disconnected + +// keep the random time dependent + rand (); + +// decide the simulation time + if (!Host_FilterTime (time)) + return; // don't run too fast, or packets will flood out + +// get new key events + Key_UpdateForDest (); + IN_UpdateInputMode (); + Sys_SendKeyEvents (); + +// allow mice or other external controllers to add commands + IN_Commands (); + +// process console commands + Cbuf_Execute (); + + NET_Poll(); + +// if running the server locally, make intentions now + if (sv.active) + CL_SendCmd (); + +//------------------- +// +// server operations +// +//------------------- + +// check for commands typed to the host + Host_GetConsoleCommands (); + + if (sv.active) + Host_ServerFrame (); + +//------------------- +// +// client operations +// +//------------------- + +// if running the server remotely, send intentions now after +// the incoming messages have been read + if (!sv.active) + CL_SendCmd (); + +// fetch results from server + if (cls.state == ca_connected) + CL_ReadFromServer (); + +// update video + if (host_speeds.value) + time1 = Sys_DoubleTime (); + + SCR_UpdateScreen (); + + CL_RunParticles (); //johnfitz -- seperated from rendering + + if (host_speeds.value) + time2 = Sys_DoubleTime (); + +// update audio + BGM_Update(); // adds music raw samples and/or advances midi driver + if (cls.signon == SIGNONS) + { + S_Update (r_origin, vpn, vright, vup); + CL_DecayLights (); + } + else + S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin); + + CDAudio_Update(); + + if (host_speeds.value) + { + pass1 = (time1 - time3)*1000; + time3 = Sys_DoubleTime (); + pass2 = (time2 - time1)*1000; + pass3 = (time3 - time2)*1000; + Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n", + pass1+pass2+pass3, pass1, pass2, pass3); + } + + host_framecount++; + +} + +void Host_Frame (float time) +{ + double time1, time2; + static double timetotal; + static int timecount; + int i, c, m; + + if (!serverprofile.value) + { + _Host_Frame (time); + return; + } + + time1 = Sys_DoubleTime (); + _Host_Frame (time); + time2 = Sys_DoubleTime (); + + timetotal += time2 - time1; + timecount++; + + if (timecount < 1000) + return; + + m = timetotal*1000/timecount; + timecount = 0; + timetotal = 0; + c = 0; + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active) + c++; + } + + Con_Printf ("serverprofile: %2i clients %2i msec\n", c, m); +} + +/* +==================== +Host_Init +==================== +*/ +void Host_Init (void) +{ + if (standard_quake) + minimum_memory = MINIMUM_MEMORY; + else minimum_memory = MINIMUM_MEMORY_LEVELPAK; + + if (COM_CheckParm ("-minmemory")) + host_parms->memsize = minimum_memory; + + if (host_parms->memsize < minimum_memory) + Sys_Error ("Only %4.1f megs of memory available, can't execute game", host_parms->memsize / (float)0x100000); + + com_argc = host_parms->argc; + com_argv = host_parms->argv; + + Memory_Init (host_parms->membase, host_parms->memsize); + Cbuf_Init (); + Cmd_Init (); + LOG_Init (host_parms); + Cvar_Init (); //johnfitz + COM_Init (); + COM_InitFilesystem (); + Host_InitLocal (); + W_LoadWadFile (); //johnfitz -- filename is now hard-coded for honesty + if (cls.state != ca_dedicated) + { + Key_Init (); + Con_Init (); + } + PR_Init (); + Mod_Init (); + NET_Init (); + SV_Init (); + + Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n"); + Con_Printf ("%4.1f megabyte heap\n", host_parms->memsize/ (1024*1024.0)); + + if (cls.state != ca_dedicated) + { + host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp", NULL); + if (!host_colormap) + Sys_Error ("Couldn't load gfx/colormap.lmp"); + + V_Init (); + Chase_Init (); + M_Init (); + ExtraMaps_Init (); //johnfitz + Modlist_Init (); //johnfitz + DemoList_Init (); //ericw + VID_Init (); + IN_Init (); + TexMgr_Init (); //johnfitz + Draw_Init (); + SCR_Init (); + R_Init (); + S_Init (); + CDAudio_Init (); + BGM_Init(); + Sbar_Init (); + CL_Init (); + } + + Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); + host_hunklevel = Hunk_LowMark (); + + host_initialized = true; + Con_Printf ("\n========= Quake Initialized =========\n\n"); + + if (cls.state != ca_dedicated) + { + Cbuf_InsertText ("exec quake.rc\n"); + // johnfitz -- in case the vid mode was locked during vid_init, we can unlock it now. + // note: two leading newlines because the command buffer swallows one of them. + Cbuf_AddText ("\n\nvid_unlock\n"); + } + + if (cls.state == ca_dedicated) + { + Cbuf_AddText ("exec autoexec.cfg\n"); + Cbuf_AddText ("stuffcmds"); + Cbuf_Execute (); + if (!sv.active) + Cbuf_AddText ("map start\n"); + } +} + + +/* +=============== +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) + { + printf ("recursive shutdown\n"); + return; + } + isdown = true; + +// keep Con_Printf from trying to update the screen + scr_disabled_for_loading = true; + + Host_WriteConfiguration (); + + NET_Shutdown (); + + if (cls.state != ca_dedicated) + { + if (con_initialized) + History_Shutdown (); + BGM_Shutdown(); + CDAudio_Shutdown (); + S_Shutdown (); + IN_Shutdown (); + VID_Shutdown(); + } + + LOG_Close (); +} + diff --git a/source/host_cmd.c b/source/host_cmd.c new file mode 100644 index 0000000..70cc0d8 --- /dev/null +++ b/source/host_cmd.c @@ -0,0 +1,2338 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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" +#ifndef _WIN32 +#include +#endif + +extern cvar_t pausable; + +int current_skill; + +void Mod_Print (void); + +/* +================== +Host_Quit_f +================== +*/ + +void Host_Quit_f (void) +{ + if (key_dest != key_console && cls.state != ca_dedicated) + { + M_Menu_Quit_f (); + return; + } + CL_Disconnect (); + Host_ShutdownServer(false); + + Sys_Quit (); +} + +//============================================================================== +//johnfitz -- extramaps management +//============================================================================== + +/* +================== +FileList_Add +================== +*/ +void FileList_Add (const char *name, filelist_item_t **list) +{ + filelist_item_t *item,*cursor,*prev; + + // ignore duplicate + for (item = *list; item; item = item->next) + { + if (!Q_strcmp (name, item->name)) + return; + } + + item = (filelist_item_t *) Z_Malloc(sizeof(filelist_item_t)); + q_strlcpy (item->name, name, sizeof(item->name)); + + // insert each entry in alphabetical order + if (*list == NULL || + q_strcasecmp(item->name, (*list)->name) < 0) //insert at front + { + item->next = *list; + *list = item; + } + else //insert later + { + prev = *list; + cursor = (*list)->next; + while (cursor && (q_strcasecmp(item->name, cursor->name) > 0)) + { + prev = cursor; + cursor = cursor->next; + } + item->next = prev->next; + prev->next = item; + } +} + +static void FileList_Clear (filelist_item_t **list) +{ + filelist_item_t *blah; + + while (*list) + { + blah = (*list)->next; + Z_Free(*list); + *list = blah; + } +} + +filelist_item_t *extralevels; + +void ExtraMaps_Add (const char *name) +{ + FileList_Add(name, &extralevels); +} + +void ExtraMaps_Init (void) +{ +#ifdef _WIN32 + WIN32_FIND_DATA fdat; + HANDLE fhnd; +#else + DIR *dir_p; + struct dirent *dir_t; +#endif + char filestring[MAX_OSPATH]; + char mapname[32]; + char ignorepakdir[32]; + searchpath_t *search; + pack_t *pak; + int i; + + // we don't want to list the maps in id1 pakfiles, + // because these are not "add-on" levels + q_snprintf (ignorepakdir, sizeof(ignorepakdir), "/%s/", GAMENAME); + + for (search = com_searchpaths; search; search = search->next) + { + if (*search->filename) //directory + { +#ifdef _WIN32 + q_snprintf (filestring, sizeof(filestring), "%s/maps/*.bsp", search->filename); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + continue; + do + { + COM_StripExtension(fdat.cFileName, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); +#else + q_snprintf (filestring, sizeof(filestring), "%s/maps/", search->filename); + dir_p = opendir(filestring); + if (dir_p == NULL) + continue; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (q_strcasecmp(COM_FileGetExtension(dir_t->d_name), "bsp") != 0) + continue; + COM_StripExtension(dir_t->d_name, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } + closedir(dir_p); +#endif + } + else //pakfile + { + if (!strstr(search->pack->filename, ignorepakdir)) + { //don't list standard id maps + for (i = 0, pak = search->pack; i < pak->numfiles; i++) + { + if (!strcmp(COM_FileGetExtension(pak->files[i].name), "bsp")) + { + if (pak->files[i].filelen > 32*1024) + { // don't list files under 32k (ammo boxes etc) + COM_StripExtension(pak->files[i].name + 5, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } + } + } + } + } + } +} + +static void ExtraMaps_Clear (void) +{ + FileList_Clear(&extralevels); +} + +void ExtraMaps_NewGame (void) +{ + ExtraMaps_Clear (); + ExtraMaps_Init (); +} + +/* +================== +Host_Maps_f +================== +*/ +void Host_Maps_f (void) +{ + int i; + filelist_item_t *level; + + for (level = extralevels, i = 0; level; level = level->next, i++) + Con_SafePrintf (" %s\n", level->name); + + if (i) + Con_SafePrintf ("%i map(s)\n", i); + else + Con_SafePrintf ("no maps found\n"); +} + +//============================================================================== +//johnfitz -- modlist management +//============================================================================== + +filelist_item_t *modlist; + +void Modlist_Add (const char *name) +{ + FileList_Add(name, &modlist); +} + +#ifdef _WIN32 +void Modlist_Init (void) +{ + WIN32_FIND_DATA fdat; + HANDLE fhnd; + DWORD attribs; + char dir_string[MAX_OSPATH], mod_string[MAX_OSPATH]; + + q_snprintf (dir_string, sizeof(dir_string), "%s/*", com_basedir); + fhnd = FindFirstFile(dir_string, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + return; + + do + { + if (!strcmp(fdat.cFileName, ".") || !strcmp(fdat.cFileName, "..")) + continue; + q_snprintf (mod_string, sizeof(mod_string), "%s/%s", com_basedir, fdat.cFileName); + attribs = GetFileAttributes (mod_string); + if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) { + /* don't bother testing for pak files / progs.dat */ + Modlist_Add(fdat.cFileName); + } + } while (FindNextFile(fhnd, &fdat)); + + FindClose(fhnd); +} +#else +void Modlist_Init (void) +{ + DIR *dir_p, *mod_dir_p; + struct dirent *dir_t; + char dir_string[MAX_OSPATH], mod_string[MAX_OSPATH]; + + q_snprintf (dir_string, sizeof(dir_string), "%s/", com_basedir); + dir_p = opendir(dir_string); + if (dir_p == NULL) + return; + + while ((dir_t = readdir(dir_p)) != NULL) + { + if (!strcmp(dir_t->d_name, ".") || !strcmp(dir_t->d_name, "..")) + continue; + if (!q_strcasecmp (COM_FileGetExtension (dir_t->d_name), "app")) // skip .app bundles on macOS + continue; + q_snprintf(mod_string, sizeof(mod_string), "%s%s/", dir_string, dir_t->d_name); + mod_dir_p = opendir(mod_string); + if (mod_dir_p == NULL) + continue; + /* don't bother testing for pak files / progs.dat */ + Modlist_Add(dir_t->d_name); + closedir(mod_dir_p); + } + + closedir(dir_p); +} +#endif + +//============================================================================== +//ericw -- demo list management +//============================================================================== + +filelist_item_t *demolist; + +static void DemoList_Clear (void) +{ + FileList_Clear (&demolist); +} + +void DemoList_Rebuild (void) +{ + DemoList_Clear (); + DemoList_Init (); +} + +// TODO: Factor out to a general-purpose file searching function +void DemoList_Init (void) +{ +#ifdef _WIN32 + WIN32_FIND_DATA fdat; + HANDLE fhnd; +#else + DIR *dir_p; + struct dirent *dir_t; +#endif + char filestring[MAX_OSPATH]; + char demname[32]; + char ignorepakdir[32]; + searchpath_t *search; + pack_t *pak; + int i; + + // we don't want to list the demos in id1 pakfiles, + // because these are not "add-on" demos + q_snprintf (ignorepakdir, sizeof(ignorepakdir), "/%s/", GAMENAME); + + for (search = com_searchpaths; search; search = search->next) + { + if (*search->filename) //directory + { +#ifdef _WIN32 + q_snprintf (filestring, sizeof(filestring), "%s/*.dem", search->filename); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + continue; + do + { + COM_StripExtension(fdat.cFileName, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); +#else + q_snprintf (filestring, sizeof(filestring), "%s/", search->filename); + dir_p = opendir(filestring); + if (dir_p == NULL) + continue; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (q_strcasecmp(COM_FileGetExtension(dir_t->d_name), "dem") != 0) + continue; + COM_StripExtension(dir_t->d_name, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } + closedir(dir_p); +#endif + } + else //pakfile + { + if (!strstr(search->pack->filename, ignorepakdir)) + { //don't list standard id demos + for (i = 0, pak = search->pack; i < pak->numfiles; i++) + { + if (!strcmp(COM_FileGetExtension(pak->files[i].name), "dem")) + { + COM_StripExtension(pak->files[i].name, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } + } + } + } + } +} + + +/* +================== +Host_Mods_f -- johnfitz + +list all potential mod directories (contain either a pak file or a progs.dat) +================== +*/ +void Host_Mods_f (void) +{ + int i; + filelist_item_t *mod; + + for (mod = modlist, i=0; mod; mod = mod->next, i++) + Con_SafePrintf (" %s\n", mod->name); + + if (i) + Con_SafePrintf ("%i mod(s)\n", i); + else + Con_SafePrintf ("no mods found\n"); +} + +//============================================================================== + +/* +============= +Host_Mapname_f -- johnfitz +============= +*/ +void Host_Mapname_f (void) +{ + if (sv.active) + { + Con_Printf ("\"mapname\" is \"%s\"\n", sv.name); + return; + } + + if (cls.state == ca_connected) + { + Con_Printf ("\"mapname\" is \"%s\"\n", cl.mapname); + return; + } + + Con_Printf ("no map loaded\n"); +} + +/* +================== +Host_Status_f +================== +*/ +void Host_Status_f (void) +{ + void (*print_fn) (const char *fmt, ...) + FUNCP_PRINTF(1,2); + client_t *client; + int seconds; + int minutes; + int hours = 0; + int j; + + if (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + print_fn = Con_Printf; + } + else + print_fn = SV_ClientPrintf; + + print_fn ("host: %s\n", Cvar_VariableString ("hostname")); + print_fn ("version: %4.2f\n", VERSION); + if (tcpipAvailable) + print_fn ("tcp/ip: %s\n", my_tcpip_address); + if (ipxAvailable) + print_fn ("ipx: %s\n", my_ipx_address); + print_fn ("map: %s\n", sv.name); + print_fn ("players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients); + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client->active) + continue; + seconds = (int)(net_time - NET_QSocketGetTime(client->netconnection)); + minutes = seconds / 60; + if (minutes) + { + seconds -= (minutes * 60); + hours = minutes / 60; + if (hours) + minutes -= (hours * 60); + } + else + hours = 0; + print_fn ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->v.frags, hours, minutes, seconds); + print_fn (" %s\n", NET_QSocketGetAddressString(client->netconnection)); + } +} + +/* +================== +Host_God_f + +Sets client to godmode +================== +*/ +void Host_God_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set god mode to on or off + switch (Cmd_Argc()) + { + case 1: + sv_player->v.flags = (int)sv_player->v.flags ^ FL_GODMODE; + if (!((int)sv_player->v.flags & FL_GODMODE) ) + SV_ClientPrintf ("godmode OFF\n"); + else + SV_ClientPrintf ("godmode ON\n"); + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.flags = (int)sv_player->v.flags | FL_GODMODE; + SV_ClientPrintf ("godmode ON\n"); + } + else + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_GODMODE; + SV_ClientPrintf ("godmode OFF\n"); + } + break; + default: + Con_Printf("god [value] : toggle god mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +/* +================== +Host_Notarget_f +================== +*/ +void Host_Notarget_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set notarget to on or off + switch (Cmd_Argc()) + { + case 1: + sv_player->v.flags = (int)sv_player->v.flags ^ FL_NOTARGET; + if (!((int)sv_player->v.flags & FL_NOTARGET) ) + SV_ClientPrintf ("notarget OFF\n"); + else + SV_ClientPrintf ("notarget ON\n"); + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.flags = (int)sv_player->v.flags | FL_NOTARGET; + SV_ClientPrintf ("notarget ON\n"); + } + else + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_NOTARGET; + SV_ClientPrintf ("notarget OFF\n"); + } + break; + default: + Con_Printf("notarget [value] : toggle notarget mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +qboolean noclip_anglehack; + +/* +================== +Host_Noclip_f +================== +*/ +void Host_Noclip_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set noclip to on or off + switch (Cmd_Argc()) + { + case 1: + if (sv_player->v.movetype != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + else + { + noclip_anglehack = false; + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("noclip OFF\n"); + } + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + else + { + noclip_anglehack = false; + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("noclip OFF\n"); + } + break; + default: + Con_Printf("noclip [value] : toggle noclip mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +/* +==================== +Host_SetPos_f + +adapted from fteqw, originally by Alex Shadowalker +==================== +*/ +void Host_SetPos_f(void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + if (Cmd_Argc() != 7 && Cmd_Argc() != 4) + { + SV_ClientPrintf("usage:\n"); + SV_ClientPrintf(" setpos \n"); + SV_ClientPrintf(" setpos \n"); + SV_ClientPrintf("current values:\n"); + SV_ClientPrintf(" %i %i %i %i %i %i\n", + (int)sv_player->v.origin[0], + (int)sv_player->v.origin[1], + (int)sv_player->v.origin[2], + (int)sv_player->v.v_angle[0], + (int)sv_player->v.v_angle[1], + (int)sv_player->v.v_angle[2]); + return; + } + + if (sv_player->v.movetype != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + + //make sure they're not going to whizz away from it + sv_player->v.velocity[0] = 0; + sv_player->v.velocity[1] = 0; + sv_player->v.velocity[2] = 0; + + sv_player->v.origin[0] = atof(Cmd_Argv(1)); + sv_player->v.origin[1] = atof(Cmd_Argv(2)); + sv_player->v.origin[2] = atof(Cmd_Argv(3)); + + if (Cmd_Argc() == 7) + { + sv_player->v.angles[0] = atof(Cmd_Argv(4)); + sv_player->v.angles[1] = atof(Cmd_Argv(5)); + sv_player->v.angles[2] = atof(Cmd_Argv(6)); + sv_player->v.fixangle = 1; + } + + SV_LinkEdict (sv_player, false); +} + +/* +================== +Host_Fly_f + +Sets client to flymode +================== +*/ +void Host_Fly_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set noclip to on or off + switch (Cmd_Argc()) + { + case 1: + if (sv_player->v.movetype != MOVETYPE_FLY) + { + sv_player->v.movetype = MOVETYPE_FLY; + SV_ClientPrintf ("flymode ON\n"); + } + else + { + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("flymode OFF\n"); + } + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.movetype = MOVETYPE_FLY; + SV_ClientPrintf ("flymode ON\n"); + } + else + { + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("flymode OFF\n"); + } + break; + default: + Con_Printf("fly [value] : toggle fly mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + + +/* +================== +Host_Ping_f + +================== +*/ +void Host_Ping_f (void) +{ + int i, j; + float total; + client_t *client; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + SV_ClientPrintf ("Client ping times:\n"); + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + if (!client->active) + continue; + total = 0; + for (j = 0; j < NUM_PING_TIMES; j++) + total+=client->ping_times[j]; + total /= NUM_PING_TIMES; + SV_ClientPrintf ("%4i %s\n", (int)(total*1000), client->name); + } +} + +/* +=============================================================================== + +SERVER TRANSITIONS + +=============================================================================== +*/ + + +/* +====================== +Host_Map_f + +handle a +map +command from the console. Active clients are kicked off. +====================== +*/ +void Host_Map_f (void) +{ + int i; + char name[MAX_QPATH], *p; + + if (Cmd_Argc() < 2) //no map name given + { + if (cls.state == ca_dedicated) + { + if (sv.active) + Con_Printf ("Current map: %s\n", sv.name); + else + Con_Printf ("Server not active\n"); + } + else if (cls.state == ca_connected) + { + Con_Printf ("Current map: %s ( %s )\n", cl.levelname, cl.mapname); + } + else + { + Con_Printf ("map : start a new server\n"); + } + return; + } + + if (cmd_source != src_command) + return; + + cls.demonum = -1; // stop demo loop in case this fails + + CL_Disconnect (); + Host_ShutdownServer(false); + + if (cls.state != ca_dedicated) + IN_Activate(); + key_dest = key_game; // remove console or menu + SCR_BeginLoadingPlaque (); + + svs.serverflags = 0; // haven't completed an episode yet + q_strlcpy (name, Cmd_Argv(1), sizeof(name)); + // remove (any) trailing ".bsp" from mapname -- S.A. + p = strstr(name, ".bsp"); + if (p && p[4] == '\0') + *p = '\0'; + SV_SpawnServer (name); + if (!sv.active) + return; + + if (cls.state != ca_dedicated) + { + memset (cls.spawnparms, 0, MAX_MAPSTRING); + for (i = 2; i < Cmd_Argc(); i++) + { + q_strlcat (cls.spawnparms, Cmd_Argv(i), MAX_MAPSTRING); + q_strlcat (cls.spawnparms, " ", MAX_MAPSTRING); + } + + Cmd_ExecuteString ("connect local", src_command); + } +} + +/* +====================== +Host_Randmap_f + +Loads a random map from the "maps" list. +====================== +*/ +void Host_Randmap_f (void) +{ + int i, randlevel, numlevels; + filelist_item_t *level; + + if (cmd_source != src_command) + return; + + for (level = extralevels, numlevels = 0; level; level = level->next) + numlevels++; + + if (numlevels == 0) + { + Con_Printf ("no maps\n"); + return; + } + + randlevel = (rand() % numlevels); + + for (level = extralevels, i = 0; level; level = level->next, i++) + { + if (i == randlevel) + { + Con_Printf ("Starting map %s...\n", level->name); + Cbuf_AddText (va("map %s\n", level->name)); + return; + } + } +} + +/* +================== +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_Printf ("changelevel : continue game on a new level\n"); + return; + } + if (!sv.active || cls.demoplayback) + { + Con_Printf ("Only the server may changelevel\n"); + return; + } + + //johnfitz -- check for client having map before anything else + q_snprintf (level, sizeof(level), "maps/%s.bsp", Cmd_Argv(1)); + if (!COM_FileExists(level, NULL)) + Host_Error ("cannot find map %s", level); + //johnfitz + + if (cls.state != ca_dedicated) + IN_Activate(); // -- S.A. + key_dest = key_game; // remove console or menu + SV_SaveSpawnparms (); + q_strlcpy (level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer (level); + // also issue an error if spawn failed -- O.S. + if (!sv.active) + Host_Error ("cannot run map %s", level); +} + +/* +================== +Host_Restart_f + +Restarts the current server for a dead player +================== +*/ +void Host_Restart_f (void) +{ + char mapname[MAX_QPATH]; + + if (cls.demoplayback || !sv.active) + return; + + if (cmd_source != src_command) + return; + q_strlcpy (mapname, sv.name, sizeof(mapname)); // mapname gets cleared in spawnserver + SV_SpawnServer (mapname); + if (!sv.active) + Host_Error ("cannot restart map %s", mapname); +} + +/* +================== +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) +{ + if (cls.demoplayback) // cross-map demo playback fix from Baker + return; + + SCR_BeginLoadingPlaque (); + cls.signon = 0; // need new connection messages +} + +/* +===================== +Host_Connect_f + +User command to connect to server +===================== +*/ +void Host_Connect_f (void) +{ + char name[MAX_QPATH]; + + cls.demonum = -1; // stop demo loop in case this fails + if (cls.demoplayback) + { + CL_StopPlayback (); + CL_Disconnect (); + } + q_strlcpy (name, Cmd_Argv(1), sizeof(name)); + CL_EstablishConnection (name); + Host_Reconnect_f (); +} + + +/* +=============================================================================== + +LOAD / SAVE GAME + +=============================================================================== +*/ + +#define SAVEGAME_VERSION 5 + +/* +=============== +Host_SavegameComment + +Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current +=============== +*/ +void Host_SavegameComment (char *text) +{ + int i; + char kills[20]; + + for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) + text[i] = ' '; + memcpy (text, cl.levelname, q_min(strlen(cl.levelname),22)); //johnfitz -- only copy 22 chars. + sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + memcpy (text+22, kills, strlen(kills)); +// convert space to _ to make stdio happy + for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) + { + if (text[i] == ' ') + text[i] = '_'; + } + text[SAVEGAME_COMMENT_LENGTH] = '\0'; +} + + +/* +=============== +Host_Savegame_f +=============== +*/ +void Host_Savegame_f (void) +{ + char name[MAX_OSPATH]; + FILE *f; + int i; + char comment[SAVEGAME_COMMENT_LENGTH+1]; + + if (cmd_source != src_command) + return; + + if (!sv.active) + { + Con_Printf ("Not playing a local game.\n"); + return; + } + + if (cl.intermission) + { + Con_Printf ("Can't save in intermission.\n"); + return; + } + + if (svs.maxclients != 1) + { + Con_Printf ("Can't save multiplayer games.\n"); + return; + } + + if (Cmd_Argc() != 2) + { + Con_Printf ("save : save a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + for (i=0 ; iv.health <= 0) ) + { + Con_Printf ("Can't savegame with a dead player\n"); + return; + } + } + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + COM_AddExtension (name, ".sav", sizeof(name)); + + Con_Printf ("Saving game to %s...\n", name); + f = fopen (name, "w"); + if (!f) + { + Con_Printf ("ERROR: couldn't open.\n"); + return; + } + + fprintf (f, "%i\n", SAVEGAME_VERSION); + Host_SavegameComment (comment); + fprintf (f, "%s\n", comment); + for (i = 0; i < NUM_SPAWN_PARMS; i++) + fprintf (f, "%f\n", svs.clients->spawn_parms[i]); + fprintf (f, "%d\n", current_skill); + fprintf (f, "%s\n", sv.name); + fprintf (f, "%f\n",sv.time); + +// write the light styles + + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + if (sv.lightstyles[i]) + fprintf (f, "%s\n", sv.lightstyles[i]); + else + fprintf (f,"m\n"); + } + + + ED_WriteGlobals (f); + for (i = 0; i < sv.num_edicts; i++) + { + ED_Write (f, EDICT_NUM(i)); + fflush (f); + } + fclose (f); + Con_Printf ("done.\n"); +} + + +/* +=============== +Host_Loadgame_f +=============== +*/ +void Host_Loadgame_f (void) +{ + static char *start; + + char name[MAX_OSPATH]; + char mapname[MAX_QPATH]; + float time, tfloat; + const char *data; + int i; + edict_t *ent; + int entnum; + int version; + float spawn_parms[NUM_SPAWN_PARMS]; + + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("load : load a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + cls.demonum = -1; // stop demo loop in case this fails + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + COM_AddExtension (name, ".sav", sizeof(name)); + +// we can't call SCR_BeginLoadingPlaque, because too much stack space has +// been used. The menu calls it before stuffing loadgame command +// SCR_BeginLoadingPlaque (); + + Con_Printf ("Loading game from %s...\n", name); + +// avoid leaking if the previous Host_Loadgame_f failed with a Host_Error + if (start != NULL) + free (start); + + start = (char *) COM_LoadMallocFile_TextMode_OSPath(name, NULL); + if (start == NULL) + { + Con_Printf ("ERROR: couldn't open.\n"); + return; + } + + data = start; + data = COM_ParseIntNewline (data, &version); + if (version != SAVEGAME_VERSION) + { + free (start); + start = NULL; + Con_Printf ("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION); + return; + } + data = COM_ParseStringNewline (data); + for (i = 0; i < NUM_SPAWN_PARMS; i++) + data = COM_ParseFloatNewline (data, &spawn_parms[i]); +// this silliness is so we can load 1.06 save files, which have float skill values + data = COM_ParseFloatNewline(data, &tfloat); + current_skill = (int)(tfloat + 0.1); + Cvar_SetValue ("skill", (float)current_skill); + + data = COM_ParseStringNewline (data); + q_strlcpy (mapname, com_token, sizeof(mapname)); + data = COM_ParseFloatNewline (data, &time); + + CL_Disconnect_f (); + + SV_SpawnServer (mapname); + + if (!sv.active) + { + free (start); + start = NULL; + Con_Printf ("Couldn't load map\n"); + return; + } + sv.paused = true; // pause until all clients connect + sv.loadgame = true; + +// load the light styles + + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + data = COM_ParseStringNewline (data); + sv.lightstyles[i] = (const char *)Hunk_Strdup (com_token, "lightstyles"); + } + +// load the edicts out of the savegame file + entnum = -1; // -1 is the globals + while (*data) + { + data = COM_Parse (data); + if (!com_token[0]) + break; // end of file + if (strcmp(com_token,"{")) + { + Sys_Error ("First token isn't a brace"); + } + + if (entnum == -1) + { // parse the global vars + data = ED_ParseGlobals (data); + } + else + { // parse an edict + ent = EDICT_NUM(entnum); + if (entnum < sv.num_edicts) { + ent->free = false; + memset (&ent->v, 0, progs->entityfields * 4); + } + else { + memset (ent, 0, pr_edict_size); + } + data = ED_ParseEdict (data, ent); + + // link it into the bsp tree + if (!ent->free) + SV_LinkEdict (ent, false); + } + + entnum++; + } + + sv.num_edicts = entnum; + sv.time = time; + + free (start); + start = NULL; + + for (i = 0; i < NUM_SPAWN_PARMS; i++) + svs.clients->spawn_parms[i] = spawn_parms[i]; + + if (cls.state != ca_dedicated) + { + CL_EstablishConnection ("local"); + Host_Reconnect_f (); + } +} + +//============================================================================ + +/* +====================== +Host_Name_f +====================== +*/ +void Host_Name_f (void) +{ + char newName[32]; + + if (Cmd_Argc () == 1) + { + Con_Printf ("\"name\" is \"%s\"\n", cl_name.string); + return; + } + if (Cmd_Argc () == 2) + q_strlcpy(newName, Cmd_Argv(1), sizeof(newName)); + else + q_strlcpy(newName, Cmd_Args(), sizeof(newName)); + newName[15] = 0; // client_t structure actually says name[32]. + + if (cmd_source == src_command) + { + if (Q_strcmp(cl_name.string, newName) == 0) + return; + Cvar_Set ("_cl_name", newName); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + if (host_client->name[0] && strcmp(host_client->name, "unconnected") ) + { + if (Q_strcmp(host_client->name, newName) != 0) + Con_Printf ("%s renamed to %s\n", host_client->name, newName); + } + Q_strcpy (host_client->name, newName); + host_client->edict->v.netname = PR_SetEngineString(host_client->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); +} + +void Host_Say(qboolean teamonly) +{ + int j; + client_t *client; + client_t *save; + const char *p; + char text[MAXCMDLINE], *p2; + qboolean quoted; + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state != ca_dedicated) + { + Cmd_ForwardToServer (); + return; + } + fromServer = true; + teamonly = false; + } + + if (Cmd_Argc () < 2) + return; + + save = host_client; + + p = Cmd_Args(); +// remove quotes if present + quoted = false; + if (*p == '\"') + { + p++; + quoted = true; + } +// turn on color set 1 + if (!fromServer) + q_snprintf (text, sizeof(text), "\001%s: %s", save->name, p); + else + q_snprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p); + +// check length & truncate if necessary + j = (int) strlen(text); + if (j >= (int) sizeof(text) - 1) + { + text[sizeof(text) - 2] = '\n'; + text[sizeof(text) - 1] = '\0'; + } + else + { + p2 = text + j; + 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--; + } + p2[0] = '\n'; + p2[1] = '\0'; + } + + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client || !client->active || !client->spawned) + continue; + if (teamplay.value && teamonly && client->edict->v.team != save->edict->v.team) + continue; + host_client = client; + SV_ClientPrintf("%s", text); + } + host_client = save; + + if (cls.state == ca_dedicated) + Sys_Printf("%s", &text[1]); +} + + +void Host_Say_f(void) +{ + Host_Say(false); +} + + +void Host_Say_Team_f(void) +{ + Host_Say(true); +} + + +void Host_Tell_f(void) +{ + int j; + client_t *client; + client_t *save; + const char *p; + char text[MAXCMDLINE], *p2; + qboolean quoted; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (Cmd_Argc () < 3) + return; + + p = Cmd_Args(); +// remove quotes if present + quoted = false; + if (*p == '\"') + { + p++; + quoted = true; + } + q_snprintf (text, sizeof(text), "%s: %s", host_client->name, p); + +// check length & truncate if necessary + j = (int) strlen(text); + if (j >= (int) sizeof(text) - 1) + { + text[sizeof(text) - 2] = '\n'; + text[sizeof(text) - 1] = '\0'; + } + else + { + p2 = text + j; + 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--; + } + p2[0] = '\n'; + p2[1] = '\0'; + } + + save = host_client; + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client->active || !client->spawned) + continue; + if (q_strcasecmp(client->name, Cmd_Argv(1))) + continue; + host_client = client; + SV_ClientPrintf("%s", text); + break; + } + host_client = save; +} + + +/* +================== +Host_Color_f +================== +*/ +void Host_Color_f(void) +{ + int top, bottom; + int playercolor; + + if (Cmd_Argc() == 1) + { + Con_Printf ("\"color\" is \"%i %i\"\n", ((int)cl_color.value) >> 4, ((int)cl_color.value) & 0x0f); + Con_Printf ("color <0-13> [0-13]\n"); + return; + } + + if (Cmd_Argc() == 2) + top = bottom = atoi(Cmd_Argv(1)); + else + { + top = atoi(Cmd_Argv(1)); + bottom = atoi(Cmd_Argv(2)); + } + + top &= 15; + if (top > 13) + top = 13; + bottom &= 15; + if (bottom > 13) + bottom = 13; + + playercolor = top*16 + bottom; + + if (cmd_source == src_command) + { + Cvar_SetValue ("_cl_color", playercolor); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + host_client->colors = playercolor; + host_client->edict->v.team = bottom + 1; + +// 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); +} + +/* +================== +Host_Kill_f +================== +*/ +void Host_Kill_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (sv_player->v.health <= 0) + { + SV_ClientPrintf ("Can't suicide -- allready dead!\n"); + return; + } + + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(sv_player); + PR_ExecuteProgram (pr_global_struct->ClientKill); +} + + +/* +================== +Host_Pause_f +================== +*/ +void Host_Pause_f (void) +{ +//ericw -- demo pause support (inspired by MarkV) + if (cls.demoplayback) + { + cls.demopaused = !cls.demopaused; + cl.paused = cls.demopaused; + return; + } + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + if (!pausable.value) + SV_ClientPrintf ("Pause not allowed.\n"); + else + { + sv.paused ^= 1; + + if (sv.paused) + { + SV_BroadcastPrintf ("%s paused the game\n", PR_GetString(sv_player->v.netname)); + } + else + { + SV_BroadcastPrintf ("%s unpaused the game\n",PR_GetString(sv_player->v.netname)); + } + + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_setpause); + MSG_WriteByte (&sv.reliable_datagram, sv.paused); + } +} + +//=========================================================================== + + +/* +================== +Host_PreSpawn_f +================== +*/ +void Host_PreSpawn_f (void) +{ + if (cmd_source == src_command) + { + Con_Printf ("prespawn is not valid from the console\n"); + return; + } + + if (host_client->spawned) + { + Con_Printf ("prespawn not valid -- allready spawned\n"); + return; + } + + SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize); + MSG_WriteByte (&host_client->message, svc_signonnum); + MSG_WriteByte (&host_client->message, 2); + host_client->sendsignon = true; +} + +/* +================== +Host_Spawn_f +================== +*/ +void Host_Spawn_f (void) +{ + int i; + client_t *client; + edict_t *ent; + + if (cmd_source == src_command) + { + Con_Printf ("spawn is not valid from the console\n"); + return; + } + + if (host_client->spawned) + { + Con_Printf ("Spawn not valid -- allready spawned\n"); + return; + } + +// run the entrance script + if (sv.loadgame) + { // loaded games are fully inited allready + // if this is the last client to be connected, unpause + sv.paused = false; + } + else + { + // set up the edict + ent = host_client->edict; + + memset (&ent->v, 0, progs->entityfields * 4); + ent->v.colormap = NUM_FOR_EDICT(ent); + ent->v.team = (host_client->colors & 15) + 1; + ent->v.netname = PR_SetEngineString(host_client->name); + + // copy spawn parms out of the client_t + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i]; + // call the spawn function + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(sv_player); + PR_ExecuteProgram (pr_global_struct->ClientConnect); + + if ((Sys_DoubleTime() - NET_QSocketGetTime(host_client->netconnection)) <= sv.time) + Sys_Printf ("%s entered the game\n", host_client->name); + + PR_ExecuteProgram (pr_global_struct->PutClientInServer); + } + + +// send all current names, colors, and frag counts + SZ_Clear (&host_client->message); + +// send time of update + MSG_WriteByte (&host_client->message, svc_time); + MSG_WriteFloat (&host_client->message, sv.time); + + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + MSG_WriteByte (&host_client->message, svc_updatename); + MSG_WriteByte (&host_client->message, i); + MSG_WriteString (&host_client->message, client->name); + MSG_WriteByte (&host_client->message, svc_updatefrags); + MSG_WriteByte (&host_client->message, i); + MSG_WriteShort (&host_client->message, client->old_frags); + MSG_WriteByte (&host_client->message, svc_updatecolors); + MSG_WriteByte (&host_client->message, i); + MSG_WriteByte (&host_client->message, client->colors); + } + +// send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + MSG_WriteByte (&host_client->message, svc_lightstyle); + MSG_WriteByte (&host_client->message, (char)i); + MSG_WriteString (&host_client->message, sv.lightstyles[i]); + } + +// +// send some stats +// + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_TOTALSECRETS); + MSG_WriteLong (&host_client->message, pr_global_struct->total_secrets); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_TOTALMONSTERS); + MSG_WriteLong (&host_client->message, pr_global_struct->total_monsters); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_SECRETS); + MSG_WriteLong (&host_client->message, pr_global_struct->found_secrets); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_MONSTERS); + MSG_WriteLong (&host_client->message, pr_global_struct->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 + ent = EDICT_NUM( 1 + (host_client - svs.clients) ); + MSG_WriteByte (&host_client->message, svc_setangle); + for (i = 0; i < 2; i++) + MSG_WriteAngle (&host_client->message, ent->v.angles[i], sv.protocolflags ); + MSG_WriteAngle (&host_client->message, 0, sv.protocolflags ); + + SV_WriteClientdataToMessage (sv_player, &host_client->message); + + MSG_WriteByte (&host_client->message, svc_signonnum); + MSG_WriteByte (&host_client->message, 3); + host_client->sendsignon = true; +} + +/* +================== +Host_Begin_f +================== +*/ +void Host_Begin_f (void) +{ + if (cmd_source == src_command) + { + Con_Printf ("begin is not valid from the console\n"); + return; + } + + host_client->spawned = true; +} + +//=========================================================================== + + +/* +================== +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 (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + } + else if (pr_global_struct->deathmatch) + return; + + save = host_client; + + if (Cmd_Argc() > 2 && Q_strcmp(Cmd_Argv(1), "#") == 0) + { + i = Q_atof(Cmd_Argv(2)) - 1; + if (i < 0 || i >= svs.maxclients) + return; + if (!svs.clients[i].active) + return; + host_client = &svs.clients[i]; + byNumber = true; + } + else + { + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + if (!host_client->active) + continue; + if (q_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 = COM_Parse(Cmd_Args()); + 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); + } + + host_client = save; +} + +/* +=============================================================================== + +DEBUGGING TOOLS + +=============================================================================== +*/ + +/* +================== +Host_Give_f +================== +*/ +void Host_Give_f (void) +{ + const char *t; + int v; + eval_t *val; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + 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 (hipnotic) + { + if (t[0] == '6') + { + if (t[1] == 'a') + sv_player->v.items = (int)sv_player->v.items | HIT_PROXIMITY_GUN; + else + sv_player->v.items = (int)sv_player->v.items | IT_GRENADE_LAUNCHER; + } + else if (t[0] == '9') + sv_player->v.items = (int)sv_player->v.items | HIT_LASER_CANNON; + else if (t[0] == '0') + sv_player->v.items = (int)sv_player->v.items | HIT_MJOLNIR; + else if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + } + else + { + if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + } + break; + + case 's': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_shells1"); + if (val) + val->_float = v; + } + sv_player->v.ammo_shells = v; + break; + + case 'n': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_nails1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } + } + else + { + sv_player->v.ammo_nails = v; + } + break; + + case 'l': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_lava_nails"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } + } + break; + + case 'r': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_rockets1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } + } + else + { + sv_player->v.ammo_rockets = v; + } + break; + + case 'm': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_multi_rockets"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } + } + break; + + case 'h': + sv_player->v.health = v; + break; + + case 'c': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_cells1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } + } + else + { + sv_player->v.ammo_cells = v; + } + break; + + case 'p': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_plasma"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } + } + break; + + //johnfitz -- give armour + case 'a': + if (v > 150) + { + sv_player->v.armortype = 0.8; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR3; + } + else if (v > 100) + { + sv_player->v.armortype = 0.6; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR2; + } + else if (v >= 0) + { + sv_player->v.armortype = 0.3; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR1; + } + break; + //johnfitz + } + + //johnfitz -- update currentammo to match new ammo (so statusbar updates correctly) + switch ((int)(sv_player->v.weapon)) + { + case IT_SHOTGUN: + case IT_SUPER_SHOTGUN: + sv_player->v.currentammo = sv_player->v.ammo_shells; + break; + case IT_NAILGUN: + case IT_SUPER_NAILGUN: + case RIT_LAVA_SUPER_NAILGUN: + sv_player->v.currentammo = sv_player->v.ammo_nails; + break; + case IT_GRENADE_LAUNCHER: + case IT_ROCKET_LAUNCHER: + case RIT_MULTI_GRENADE: + case RIT_MULTI_ROCKET: + sv_player->v.currentammo = sv_player->v.ammo_rockets; + break; + case IT_LIGHTNING: + case HIT_LASER_CANNON: + case HIT_MJOLNIR: + sv_player->v.currentammo = sv_player->v.ammo_cells; + break; + case RIT_LAVA_NAILGUN: //same as IT_AXE + if (rogue) + sv_player->v.currentammo = sv_player->v.ammo_nails; + break; + case RIT_PLASMA_GUN: //same as HIT_PROXIMITY_GUN + if (rogue) + sv_player->v.currentammo = sv_player->v.ammo_cells; + if (hipnotic) + sv_player->v.currentammo = sv_player->v.ammo_rockets; + break; + } + //johnfitz +} + +edict_t *FindViewthing (void) +{ + int i; + edict_t *e; + + for (i=0 ; iv.classname), "viewthing") ) + return e; + } + Con_Printf ("No viewthing on map\n"); + return NULL; +} + +/* +================== +Host_Viewmodel_f +================== +*/ +void Host_Viewmodel_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + + m = Mod_ForName (Cmd_Argv(1), false); + if (!m) + { + Con_Printf ("Can't load %s\n", Cmd_Argv(1)); + return; + } + + e->v.frame = 0; + cl.model_precache[(int)e->v.modelindex] = m; +} + +/* +================== +Host_Viewframe_f +================== +*/ +void Host_Viewframe_f (void) +{ + edict_t *e; + int f; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + m = cl.model_precache[(int)e->v.modelindex]; + + f = atoi(Cmd_Argv(1)); + if (f >= m->numframes) + f = m->numframes - 1; + + e->v.frame = f; +} + + +void PrintFrameName (qmodel_t *m, int frame) +{ + aliashdr_t *hdr; + maliasframedesc_t *pframedesc; + + hdr = (aliashdr_t *)Mod_Extradata (m); + if (!hdr) + return; + pframedesc = &hdr->frames[frame]; + + Con_Printf ("frame %i: %s\n", frame, pframedesc->name); +} + +/* +================== +Host_Viewnext_f +================== +*/ +void Host_Viewnext_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + m = cl.model_precache[(int)e->v.modelindex]; + + e->v.frame = e->v.frame + 1; + if (e->v.frame >= m->numframes) + e->v.frame = m->numframes - 1; + + PrintFrameName (m, e->v.frame); +} + +/* +================== +Host_Viewprev_f +================== +*/ +void Host_Viewprev_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + + m = cl.model_precache[(int)e->v.modelindex]; + + e->v.frame = e->v.frame - 1; + if (e->v.frame < 0) + e->v.frame = 0; + + PrintFrameName (m, e->v.frame); +} + +/* +=============================================================================== + +DEMO LOOP CONTROL + +=============================================================================== +*/ + + +/* +================== +Host_Startdemos_f +================== +*/ +void Host_Startdemos_f (void) +{ + int i, c; + + if (cls.state == ca_dedicated) + return; + + c = Cmd_Argc() - 1; + if (c > MAX_DEMOS) + { + Con_Printf ("Max %i demos in demoloop\n", MAX_DEMOS); + c = MAX_DEMOS; + } + Con_Printf ("%i demo(s) in loop\n", c); + + for (i = 1; i < c + 1; i++) + q_strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof(cls.demos[0])); + + if (!sv.active && cls.demonum != -1 && !cls.demoplayback) + { + cls.demonum = 0; + if (!fitzmode) + { /* QuakeSpasm customization: */ + /* go straight to menu, no CL_NextDemo */ + cls.demonum = -1; + Cbuf_InsertText("menu_main\n"); + return; + } + CL_NextDemo (); + } + else + { + cls.demonum = -1; + } +} + + +/* +================== +Host_Demos_f + +Return to looping demos +================== +*/ +void Host_Demos_f (void) +{ + if (cls.state == ca_dedicated) + return; + if (cls.demonum == -1) + cls.demonum = 1; + CL_Disconnect_f (); + CL_NextDemo (); +} + +/* +================== +Host_Stopdemo_f + +Return to looping demos +================== +*/ +void Host_Stopdemo_f (void) +{ + if (cls.state == ca_dedicated) + return; + if (!cls.demoplayback) + return; + CL_StopPlayback (); + CL_Disconnect (); +} + +//============================================================================= + +/* +================== +Host_InitCommands +================== +*/ +void Host_InitCommands (void) +{ + Cmd_AddCommand ("maps", Host_Maps_f); //johnfitz + Cmd_AddCommand ("mods", Host_Mods_f); //johnfitz + Cmd_AddCommand ("games", Host_Mods_f); // as an alias to "mods" -- S.A. / QuakeSpasm + Cmd_AddCommand ("mapname", Host_Mapname_f); //johnfitz + Cmd_AddCommand ("randmap", Host_Randmap_f); //ericw + + Cmd_AddCommand ("status", Host_Status_f); + Cmd_AddCommand ("quit", Host_Quit_f); + Cmd_AddCommand ("god", Host_God_f); + Cmd_AddCommand ("notarget", Host_Notarget_f); + Cmd_AddCommand ("fly", Host_Fly_f); + Cmd_AddCommand ("map", Host_Map_f); + Cmd_AddCommand ("restart", Host_Restart_f); + Cmd_AddCommand ("changelevel", Host_Changelevel_f); + Cmd_AddCommand ("connect", Host_Connect_f); + Cmd_AddCommand ("reconnect", Host_Reconnect_f); + Cmd_AddCommand ("name", Host_Name_f); + Cmd_AddCommand ("noclip", Host_Noclip_f); + Cmd_AddCommand ("setpos", Host_SetPos_f); //QuakeSpasm + + Cmd_AddCommand ("say", Host_Say_f); + Cmd_AddCommand ("say_team", Host_Say_Team_f); + Cmd_AddCommand ("tell", Host_Tell_f); + Cmd_AddCommand ("color", Host_Color_f); + Cmd_AddCommand ("kill", Host_Kill_f); + Cmd_AddCommand ("pause", Host_Pause_f); + Cmd_AddCommand ("spawn", Host_Spawn_f); + Cmd_AddCommand ("begin", Host_Begin_f); + Cmd_AddCommand ("prespawn", Host_PreSpawn_f); + Cmd_AddCommand ("kick", Host_Kick_f); + Cmd_AddCommand ("ping", Host_Ping_f); + Cmd_AddCommand ("load", Host_Loadgame_f); + Cmd_AddCommand ("save", Host_Savegame_f); + Cmd_AddCommand ("give", Host_Give_f); + + Cmd_AddCommand ("startdemos", Host_Startdemos_f); + Cmd_AddCommand ("demos", Host_Demos_f); + Cmd_AddCommand ("stopdemo", Host_Stopdemo_f); + + Cmd_AddCommand ("viewmodel", Host_Viewmodel_f); + Cmd_AddCommand ("viewframe", Host_Viewframe_f); + Cmd_AddCommand ("viewnext", Host_Viewnext_f); + Cmd_AddCommand ("viewprev", Host_Viewprev_f); + + Cmd_AddCommand ("mcache", Mod_Print); +} + diff --git a/source/image.c b/source/image.c new file mode 100644 index 0000000..399b6eb --- /dev/null +++ b/source/image.c @@ -0,0 +1,601 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +//image.c -- image loading + +#include "quakedef.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_STATIC +#include "stb_image_write.h" + +#define LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_NO_COMPILE_CPP +#define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_NO_COMPILE_ERROR_TEXT +#include "lodepng.h" +#include "lodepng.c" + +static char loadfilename[MAX_OSPATH]; //file scope so that error messages can use it + +typedef struct stdio_buffer_s { + FILE *f; + unsigned char buffer[1024]; + int size; + int pos; +} stdio_buffer_t; + +static stdio_buffer_t *Buf_Alloc(FILE *f) +{ + stdio_buffer_t *buf = (stdio_buffer_t *) calloc(1, sizeof(stdio_buffer_t)); + buf->f = f; + return buf; +} + +static void Buf_Free(stdio_buffer_t *buf) +{ + free(buf); +} + +static inline int Buf_GetC(stdio_buffer_t *buf) +{ + if (buf->pos >= buf->size) + { + buf->size = fread(buf->buffer, 1, sizeof(buf->buffer), buf->f); + buf->pos = 0; + + if (buf->size == 0) + return EOF; + } + + return buf->buffer[buf->pos++]; +} + +/* +============ +Image_LoadImage + +returns a pointer to hunk allocated RGBA data + +TODO: search order: tga png jpg pcx lmp +============ +*/ +byte *Image_LoadImage (const char *name, int *width, int *height) +{ + FILE *f; + + q_snprintf (loadfilename, sizeof(loadfilename), "%s.tga", name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadTGA (f, width, height); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s.pcx", name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadPCX (f, width, height); + + return NULL; +} + +//============================================================================== +// +// TGA +// +//============================================================================== + +typedef struct targaheader_s { + 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_t; + +#define TARGAHEADERSIZE 18 //size on disk + +targaheader_t targa_header; + +int fgetLittleShort (FILE *f) +{ + byte b1, b2; + + b1 = fgetc(f); + b2 = fgetc(f); + + return (short)(b1 + b2*256); +} + +int fgetLittleLong (FILE *f) +{ + byte b1, b2, b3, b4; + + b1 = fgetc(f); + b2 = fgetc(f); + b3 = fgetc(f); + b4 = fgetc(f); + + return b1 + (b2<<8) + (b3<<16) + (b4<<24); +} + +/* +============ +Image_WriteTGA -- writes RGB or RGBA data to a TGA file + +returns true if successful + +TODO: support BGRA and BGR formats (since opengl can return them, and we don't have to swap) +============ +*/ +qboolean Image_WriteTGA (const char *name, byte *data, int width, int height, int bpp, qboolean upsidedown) +{ + int handle, i, size, temp, bytes; + char pathname[MAX_OSPATH]; + byte header[TARGAHEADERSIZE]; + + Sys_mkdir (com_gamedir); //if we've switched to a nonexistant gamedir, create it now so we don't crash + q_snprintf (pathname, sizeof(pathname), "%s/%s", com_gamedir, name); + handle = Sys_FileOpenWrite (pathname); + if (handle == -1) + return false; + + Q_memset (header, 0, TARGAHEADERSIZE); + header[2] = 2; // uncompressed type + header[12] = width&255; + header[13] = width>>8; + header[14] = height&255; + header[15] = height>>8; + header[16] = bpp; // pixel size + if (upsidedown) + header[17] = 0x20; //upside-down attribute + + // swap red and blue bytes + bytes = bpp/8; + size = width*height*bytes; + for (i=0; i=0; row--) + { + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + for(column=0; column=0; row--) + { + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + for(column=0; column0) + row--; + else + goto breakOut; + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + } + } + } + else // non run-length packet + { + for(j=0;j0) + row--; + else + goto breakOut; + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + } + } + } + } + breakOut:; + } + } + + Buf_Free(buf); + fclose(fin); + + *width = (int)(targa_header.width); + *height = (int)(targa_header.height); + return targa_rgba; +} + +//============================================================================== +// +// PCX +// +//============================================================================== + +typedef struct +{ + char signature; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hdpi,vdpi; + byte colortable[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; +} pcxheader_t; + +/* +============ +Image_LoadPCX +============ +*/ +byte *Image_LoadPCX (FILE *f, int *width, int *height) +{ + pcxheader_t pcx; + int x, y, w, h, readbyte, runlength, start; + byte *p, *data; + byte palette[768]; + stdio_buffer_t *buf; + + start = ftell (f); //save start of file (since we might be inside a pak file, SEEK_SET might not be the start of the pcx) + + fread(&pcx, sizeof(pcx), 1, f); + pcx.xmin = (unsigned short)LittleShort (pcx.xmin); + pcx.ymin = (unsigned short)LittleShort (pcx.ymin); + pcx.xmax = (unsigned short)LittleShort (pcx.xmax); + pcx.ymax = (unsigned short)LittleShort (pcx.ymax); + pcx.bytes_per_line = (unsigned short)LittleShort (pcx.bytes_per_line); + + if (pcx.signature != 0x0A) + Sys_Error ("'%s' is not a valid PCX file", loadfilename); + + if (pcx.version != 5) + Sys_Error ("'%s' is version %i, should be 5", loadfilename, pcx.version); + + if (pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcx.color_planes != 1) + Sys_Error ("'%s' has wrong encoding or bit depth", loadfilename); + + w = pcx.xmax - pcx.xmin + 1; + h = pcx.ymax - pcx.ymin + 1; + + data = (byte *) Hunk_Alloc((w*h+1)*4); //+1 to allow reading padding byte on last line + + //load palette + fseek (f, start + com_filesize - 768, SEEK_SET); + fread (palette, 1, 768, f); + + //back to start of image data + fseek (f, start + sizeof(pcx), SEEK_SET); + + buf = Buf_Alloc(f); + + for (y=0; y= 0xC0) + { + runlength = readbyte & 0x3F; + readbyte = Buf_GetC(buf); + } + else + runlength = 1; + + while(runlength--) + { + p[0] = palette[readbyte*3]; + p[1] = palette[readbyte*3+1]; + p[2] = palette[readbyte*3+2]; + p[3] = 255; + p += 4; + x++; + } + } + } + + Buf_Free(buf); + fclose(f); + + *width = w; + *height = h; + return data; +} + +//============================================================================== +// +// STB_IMAGE_WRITE +// +//============================================================================== + +static byte *CopyFlipped(const byte *data, int width, int height, int bpp) +{ + int y, rowsize; + byte *flipped; + + rowsize = width * (bpp / 8); + flipped = (byte *) malloc(height * rowsize); + if (!flipped) + return NULL; + + for (y=0; y + +static qboolean textmode; + +static cvar_t in_debugkeys = {"in_debugkeys", "0", CVAR_NONE}; + +#ifdef __APPLE__ +/* Mouse acceleration needs to be disabled on OS X */ +#define MACOS_X_ACCELERATION_HACK +#endif + +#ifdef MACOS_X_ACCELERATION_HACK +#include +#include +#include +#include +#endif + +// SDL2 Game Controller cvars +cvar_t joy_deadzone = { "joy_deadzone", "0.175", CVAR_ARCHIVE }; +cvar_t joy_deadzone_trigger = { "joy_deadzone_trigger", "0.2", CVAR_ARCHIVE }; +cvar_t joy_sensitivity_yaw = { "joy_sensitivity_yaw", "300", CVAR_ARCHIVE }; +cvar_t joy_sensitivity_pitch = { "joy_sensitivity_pitch", "150", CVAR_ARCHIVE }; +cvar_t joy_invert = { "joy_invert", "0", CVAR_ARCHIVE }; +cvar_t joy_exponent = { "joy_exponent", "3", CVAR_ARCHIVE }; +cvar_t joy_exponent_move = { "joy_exponent_move", "3", CVAR_ARCHIVE }; +cvar_t joy_swapmovelook = { "joy_swapmovelook", "0", CVAR_ARCHIVE }; +cvar_t joy_enable = { "joy_enable", "1", CVAR_ARCHIVE }; + +static SDL_JoystickID joy_active_instaceid = -1; +static SDL_GameController *joy_active_controller = NULL; + +static qboolean no_mouse = false; + +static int buttonremap[] = +{ + K_MOUSE1, + K_MOUSE3, /* right button */ + K_MOUSE2, /* middle button */ + K_MOUSE4, + K_MOUSE5 +}; + +/* total accumulated mouse movement since last frame */ +static int total_dx, total_dy = 0; + +static int SDLCALL IN_FilterMouseEvents (const SDL_Event *event) +{ + switch (event->type) + { + case SDL_MOUSEMOTION: + // case SDL_MOUSEBUTTONDOWN: + // case SDL_MOUSEBUTTONUP: + return 0; + } + + return 1; +} + +static int SDLCALL IN_SDL2_FilterMouseEvents (void *userdata, SDL_Event *event) +{ + return IN_FilterMouseEvents (event); +} + +static void IN_BeginIgnoringMouseEvents(void) +{ + SDL_EventFilter currentFilter = NULL; + void *currentUserdata = NULL; + SDL_GetEventFilter(¤tFilter, ¤tUserdata); + + if (currentFilter != IN_SDL2_FilterMouseEvents) + SDL_SetEventFilter(IN_SDL2_FilterMouseEvents, NULL); +} + +static void IN_EndIgnoringMouseEvents(void) +{ + SDL_EventFilter currentFilter; + void *currentUserdata; + if (SDL_GetEventFilter(¤tFilter, ¤tUserdata) == SDL_TRUE) + SDL_SetEventFilter(NULL, NULL); +} + +#ifdef MACOS_X_ACCELERATION_HACK +static cvar_t in_disablemacosxmouseaccel = {"in_disablemacosxmouseaccel", "1", CVAR_ARCHIVE}; +static double originalMouseSpeed = -1.0; + +static io_connect_t IN_GetIOHandle(void) +{ + io_connect_t iohandle = MACH_PORT_NULL; + io_service_t iohidsystem = MACH_PORT_NULL; + mach_port_t masterport; + kern_return_t status; + + 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; +} + +static void IN_DisableOSXMouseAccel (void) +{ + io_connect_t mouseDev = IN_GetIOHandle(); + if (mouseDev != 0) + { + if (IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) + { + if (IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + } + } + else + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); + } + IOServiceClose(mouseDev); + } + else + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); + } +} + +static void IN_ReenableOSXMouseAccel (void) +{ + io_connect_t mouseDev = IN_GetIOHandle(); + if (mouseDev != 0) + { + if (IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) + Con_Printf("WARNING: Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + IOServiceClose(mouseDev); + } + else + { + Con_Printf("WARNING: Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); + } + originalMouseSpeed = -1; +} +#endif /* MACOS_X_ACCELERATION_HACK */ + + +void IN_Activate (void) +{ + if (no_mouse) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + /* Save the status of mouse acceleration */ + if (originalMouseSpeed == -1 && in_disablemacosxmouseaccel.value) + IN_DisableOSXMouseAccel(); +#endif + + if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0) + { + Con_Printf("WARNING: SDL_SetRelativeMouseMode(SDL_TRUE) failed.\n"); + } + + IN_EndIgnoringMouseEvents(); + + total_dx = 0; + total_dy = 0; +} + +void IN_Deactivate (qboolean free_cursor) +{ + if (no_mouse) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + if (originalMouseSpeed != -1) + IN_ReenableOSXMouseAccel(); +#endif + + if (free_cursor) + { + SDL_SetRelativeMouseMode(SDL_FALSE); + } + + /* discard all mouse events when input is deactivated */ + IN_BeginIgnoringMouseEvents(); +} + +void IN_StartupJoystick (void) +{ + int i; + int nummappings; + char controllerdb[MAX_OSPATH]; + SDL_GameController *gamecontroller; + + if (COM_CheckParm("-nojoy")) + return; + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1 ) + { + Con_Warning("could not initialize SDL Game Controller\n"); + return; + } + + // Load additional SDL2 controller definitions from gamecontrollerdb.txt + q_snprintf (controllerdb, sizeof(controllerdb), "%s/gamecontrollerdb.txt", com_basedir); + nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); + if (nummappings > 0) + Con_Printf("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); + + // Also try host_parms->userdir + if (host_parms->userdir != host_parms->basedir) + { + q_snprintf (controllerdb, sizeof(controllerdb), "%s/gamecontrollerdb.txt", host_parms->userdir); + nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); + if (nummappings > 0) + Con_Printf("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); + } + + for (i = 0; i < SDL_NumJoysticks(); i++) + { + const char *joyname = SDL_JoystickNameForIndex(i); + if ( SDL_IsGameController(i) ) + { + const char *controllername = SDL_GameControllerNameForIndex(i); + gamecontroller = SDL_GameControllerOpen(i); + if (gamecontroller) + { + Con_Printf("detected controller: %s\n", controllername != NULL ? controllername : "NULL"); + + joy_active_instaceid = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamecontroller)); + joy_active_controller = gamecontroller; + break; + } + else + { + Con_Warning("failed to open controller: %s\n", controllername != NULL ? controllername : "NULL"); + } + } + else + { + Con_Warning("joystick missing controller mappings: %s\n", joyname != NULL ? joyname : "NULL" ); + } + } +} + +void IN_ShutdownJoystick (void) +{ + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); +} + +void IN_Init (void) +{ + textmode = Key_TextEntry(); + + if (textmode) + SDL_StartTextInput(); + else + SDL_StopTextInput(); + if (safemode || COM_CheckParm("-nomouse")) + { + no_mouse = true; + /* discard all mouse events when input is deactivated */ + IN_BeginIgnoringMouseEvents(); + } + +#ifdef MACOS_X_ACCELERATION_HACK + Cvar_RegisterVariable(&in_disablemacosxmouseaccel); +#endif + Cvar_RegisterVariable(&in_debugkeys); + Cvar_RegisterVariable(&joy_sensitivity_yaw); + Cvar_RegisterVariable(&joy_sensitivity_pitch); + Cvar_RegisterVariable(&joy_deadzone); + Cvar_RegisterVariable(&joy_deadzone_trigger); + Cvar_RegisterVariable(&joy_invert); + Cvar_RegisterVariable(&joy_exponent); + Cvar_RegisterVariable(&joy_exponent_move); + Cvar_RegisterVariable(&joy_swapmovelook); + Cvar_RegisterVariable(&joy_enable); + + IN_Activate(); + IN_StartupJoystick(); +} + +void IN_Shutdown (void) +{ + IN_Deactivate(true); + IN_ShutdownJoystick(); +} + +extern cvar_t cl_maxpitch; /* johnfitz -- variable pitch clamping */ +extern cvar_t cl_minpitch; /* johnfitz -- variable pitch clamping */ + + +void IN_MouseMotion(int dx, int dy) +{ + total_dx += dx; + total_dy += dy; +} + +typedef struct joyaxis_s +{ + float x; + float y; +} joyaxis_t; + +typedef struct joy_buttonstate_s +{ + qboolean buttondown[SDL_CONTROLLER_BUTTON_MAX]; +} joybuttonstate_t; + +typedef struct axisstate_s +{ + float axisvalue[SDL_CONTROLLER_AXIS_MAX]; // normalized to +-1 +} joyaxisstate_t; + +static joybuttonstate_t joy_buttonstate; +static joyaxisstate_t joy_axisstate; + +static double joy_buttontimer[SDL_CONTROLLER_BUTTON_MAX]; +static double joy_emulatedkeytimer[10]; + +#ifdef __WATCOMC__ /* OW1.9 doesn't have powf() / sqrtf() */ +#define powf pow +#define sqrtf sqrt +#endif + +/* +================ +IN_AxisMagnitude + +Returns the vector length of the given joystick axis +================ +*/ +static vec_t IN_AxisMagnitude(joyaxis_t axis) +{ + vec_t magnitude = sqrtf((axis.x * axis.x) + (axis.y * axis.y)); + return magnitude; +} + +/* +================ +IN_ApplyEasing + +assumes axis values are in [-1, 1] and the vector magnitude has been clamped at 1. +Raises the axis values to the given exponent, keeping signs. +================ +*/ +static joyaxis_t IN_ApplyEasing(joyaxis_t axis, float exponent) +{ + joyaxis_t result = {0}; + vec_t eased_magnitude; + vec_t magnitude = IN_AxisMagnitude(axis); + + if (magnitude == 0) + return result; + + eased_magnitude = powf(magnitude, exponent); + + result.x = axis.x * (eased_magnitude / magnitude); + result.y = axis.y * (eased_magnitude / magnitude); + return result; +} + +/* +================ +IN_ApplyMoveEasing + +same as IN_ApplyEasing, but scales the output by sqrt(2). +this gives diagonal stick inputs coordinates of (+/-1,+/-1). + +forward/back/left/right will return +/- 1.41; this shouldn't be a problem because +you can pull back on the stick to go slower (and the final speed is clamped +by sv_maxspeed). +================ +*/ +static joyaxis_t IN_ApplyMoveEasing(joyaxis_t axis, float exponent) +{ + joyaxis_t result = IN_ApplyEasing(axis, exponent); + const float v = sqrtf(2.0f); + + result.x *= v; + result.y *= v; + + return result; +} + +/* +================ +IN_ApplyDeadzone + +in: raw joystick axis values converted to floats in +-1 +out: applies a circular deadzone and clamps the magnitude at 1 + (my 360 controller is slightly non-circular and the stick travels further on the diagonals) + +deadzone is expected to satisfy 0 < deadzone < 1 + +from https://github.com/jeremiah-sypult/Quakespasm-Rift +and adapted from http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html +================ +*/ +static joyaxis_t IN_ApplyDeadzone(joyaxis_t axis, float deadzone) +{ + joyaxis_t result = {0}; + vec_t magnitude = IN_AxisMagnitude(axis); + + if ( magnitude > deadzone ) { + const vec_t new_magnitude = q_min(1.0, (magnitude - deadzone) / (1.0 - deadzone)); + const vec_t scale = new_magnitude / magnitude; + result.x = axis.x * scale; + result.y = axis.y * scale; + } + + return result; +} + +/* +================ +IN_KeyForControllerButton +================ +*/ +static int IN_KeyForControllerButton(SDL_GameControllerButton button) +{ + switch (button) + { + case SDL_CONTROLLER_BUTTON_A: return K_ABUTTON; + case SDL_CONTROLLER_BUTTON_B: return K_BBUTTON; + case SDL_CONTROLLER_BUTTON_X: return K_XBUTTON; + case SDL_CONTROLLER_BUTTON_Y: return K_YBUTTON; + case SDL_CONTROLLER_BUTTON_BACK: return K_TAB; + case SDL_CONTROLLER_BUTTON_START: return K_ESCAPE; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: return K_LTHUMB; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return K_RTHUMB; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return K_LSHOULDER; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return K_RSHOULDER; + case SDL_CONTROLLER_BUTTON_DPAD_UP: return K_UPARROW; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return K_DOWNARROW; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return K_LEFTARROW; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return K_RIGHTARROW; + default: return 0; + } +} + +/* +================ +IN_JoyKeyEvent + +Sends a Key_Event if a unpressed -> pressed or pressed -> unpressed transition occurred, +and generates key repeats if the button is held down. + +Adapted from DarkPlaces by lordhavoc +================ +*/ +static void IN_JoyKeyEvent(qboolean wasdown, qboolean isdown, int key, double *timer) +{ + // we can't use `realtime` for key repeats because it is not monotomic + const double currenttime = Sys_DoubleTime(); + + if (wasdown) + { + if (isdown) + { + if (currenttime >= *timer) + { + *timer = currenttime + 0.1; + Key_Event(key, true); + } + } + else + { + *timer = 0; + Key_Event(key, false); + } + } + else + { + if (isdown) + { + *timer = currenttime + 0.5; + Key_Event(key, true); + } + } +} + +/* +================ +IN_Commands + +Emit key events for game controller buttons, including emulated buttons for analog sticks/triggers +================ +*/ +void IN_Commands (void) +{ + joyaxisstate_t newaxisstate; + int i; + const float stickthreshold = 0.9; + const float triggerthreshold = joy_deadzone_trigger.value; + + if (!joy_enable.value) + return; + + if (!joy_active_controller) + return; + + // emit key events for controller buttons + for (i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + qboolean newstate = SDL_GameControllerGetButton(joy_active_controller, (SDL_GameControllerButton)i); + qboolean oldstate = joy_buttonstate.buttondown[i]; + + joy_buttonstate.buttondown[i] = newstate; + + // NOTE: This can cause a reentrant call of IN_Commands, via SCR_ModalMessage when confirming a new game. + IN_JoyKeyEvent(oldstate, newstate, IN_KeyForControllerButton((SDL_GameControllerButton)i), &joy_buttontimer[i]); + } + + for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) + { + newaxisstate.axisvalue[i] = SDL_GameControllerGetAxis(joy_active_controller, (SDL_GameControllerAxis)i) / 32768.0f; + } + + // emit emulated arrow keys so the analog sticks can be used in the menu + if (key_dest != key_game) + { + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] < -stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] < -stickthreshold, K_LEFTARROW, &joy_emulatedkeytimer[0]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] > stickthreshold, K_RIGHTARROW, &joy_emulatedkeytimer[1]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] < -stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] < -stickthreshold, K_UPARROW, &joy_emulatedkeytimer[2]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] > stickthreshold, K_DOWNARROW, &joy_emulatedkeytimer[3]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX] < -stickthreshold,newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX] < -stickthreshold, K_LEFTARROW, &joy_emulatedkeytimer[4]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX] > stickthreshold, K_RIGHTARROW, &joy_emulatedkeytimer[5]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY] < -stickthreshold,newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY] < -stickthreshold, K_UPARROW, &joy_emulatedkeytimer[6]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY] > stickthreshold, K_DOWNARROW, &joy_emulatedkeytimer[7]); + } + + // emit emulated keys for the analog triggers + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > triggerthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > triggerthreshold, K_LTRIGGER, &joy_emulatedkeytimer[8]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > triggerthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > triggerthreshold, K_RTRIGGER, &joy_emulatedkeytimer[9]); + + joy_axisstate = newaxisstate; +} + +/* +================ +IN_JoyMove +================ +*/ +void IN_JoyMove (usercmd_t *cmd) +{ + float speed; + joyaxis_t moveRaw, moveDeadzone, moveEased; + joyaxis_t lookRaw, lookDeadzone, lookEased; + + if (!joy_enable.value) + return; + + if (!joy_active_controller) + return; + + moveRaw.x = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX]; + moveRaw.y = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY]; + lookRaw.x = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX]; + lookRaw.y = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY]; + + if (joy_swapmovelook.value) + { + joyaxis_t temp = moveRaw; + moveRaw = lookRaw; + lookRaw = temp; + } + + moveDeadzone = IN_ApplyDeadzone(moveRaw, joy_deadzone.value); + lookDeadzone = IN_ApplyDeadzone(lookRaw, joy_deadzone.value); + + moveEased = IN_ApplyMoveEasing(moveDeadzone, joy_exponent_move.value); + lookEased = IN_ApplyEasing(lookDeadzone, joy_exponent.value); + + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0)) + speed = cl_movespeedkey.value; + else + speed = 1; + + cmd->sidemove += (cl_sidespeed.value * speed * moveEased.x); + cmd->forwardmove -= (cl_forwardspeed.value * speed * moveEased.y); + + cl.viewangles[YAW] -= lookEased.x * joy_sensitivity_yaw.value * host_frametime; + cl.viewangles[PITCH] += lookEased.y * joy_sensitivity_pitch.value * (joy_invert.value ? -1.0 : 1.0) * host_frametime; + + if (lookEased.x != 0 || lookEased.y != 0) + V_StopPitchDrift(); + + /* johnfitz -- variable pitch clamping */ + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; +} + +void IN_MouseMove(usercmd_t *cmd) +{ + int dmx, dmy; + + dmx = total_dx * sensitivity.value; + dmy = total_dy * sensitivity.value; + + total_dx = 0; + total_dy = 0; + + if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) )) + cmd->sidemove += m_side.value * dmx; + else + cl.viewangles[YAW] -= m_yaw.value * dmx; + + if (in_mlook.state & 1) + { + if (dmx || dmy) + V_StopPitchDrift (); + } + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + cl.viewangles[PITCH] += m_pitch.value * dmy; + /* johnfitz -- variable pitch clamping */ + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; + } + else + { + if ((in_strafe.state & 1) && noclip_anglehack) + cmd->upmove -= m_forward.value * dmy; + else + cmd->forwardmove -= m_forward.value * dmy; + } +} + +void IN_Move(usercmd_t *cmd) +{ + IN_JoyMove(cmd); + IN_MouseMove(cmd); +} + +void IN_ClearStates (void) +{ +} + +void IN_UpdateInputMode (void) +{ + qboolean want_textmode = Key_TextEntry(); + if (textmode != want_textmode) + { + textmode = want_textmode; + if (textmode) + { + SDL_StartTextInput(); + if (in_debugkeys.value) + Con_Printf("SDL_StartTextInput time: %g\n", Sys_DoubleTime()); + } + else + { + SDL_StopTextInput(); + if (in_debugkeys.value) + Con_Printf("SDL_StopTextInput time: %g\n", Sys_DoubleTime()); + } + } +} + +static inline int IN_SDL2_ScancodeToQuakeKey(SDL_Scancode scancode) +{ + switch (scancode) + { + case SDL_SCANCODE_TAB: return K_TAB; + case SDL_SCANCODE_RETURN: return K_ENTER; + case SDL_SCANCODE_RETURN2: return K_ENTER; + case SDL_SCANCODE_ESCAPE: return K_ESCAPE; + case SDL_SCANCODE_SPACE: return K_SPACE; + + case SDL_SCANCODE_A: return 'a'; + case SDL_SCANCODE_B: return 'b'; + case SDL_SCANCODE_C: return 'c'; + case SDL_SCANCODE_D: return 'd'; + case SDL_SCANCODE_E: return 'e'; + case SDL_SCANCODE_F: return 'f'; + case SDL_SCANCODE_G: return 'g'; + case SDL_SCANCODE_H: return 'h'; + case SDL_SCANCODE_I: return 'i'; + case SDL_SCANCODE_J: return 'j'; + case SDL_SCANCODE_K: return 'k'; + case SDL_SCANCODE_L: return 'l'; + case SDL_SCANCODE_M: return 'm'; + case SDL_SCANCODE_N: return 'n'; + case SDL_SCANCODE_O: return 'o'; + case SDL_SCANCODE_P: return 'p'; + case SDL_SCANCODE_Q: return 'q'; + case SDL_SCANCODE_R: return 'r'; + case SDL_SCANCODE_S: return 's'; + case SDL_SCANCODE_T: return 't'; + case SDL_SCANCODE_U: return 'u'; + case SDL_SCANCODE_V: return 'v'; + case SDL_SCANCODE_W: return 'w'; + case SDL_SCANCODE_X: return 'x'; + case SDL_SCANCODE_Y: return 'y'; + case SDL_SCANCODE_Z: return 'z'; + + case SDL_SCANCODE_1: return '1'; + case SDL_SCANCODE_2: return '2'; + case SDL_SCANCODE_3: return '3'; + case SDL_SCANCODE_4: return '4'; + case SDL_SCANCODE_5: return '5'; + case SDL_SCANCODE_6: return '6'; + case SDL_SCANCODE_7: return '7'; + case SDL_SCANCODE_8: return '8'; + case SDL_SCANCODE_9: return '9'; + case SDL_SCANCODE_0: return '0'; + + case SDL_SCANCODE_MINUS: return '-'; + case SDL_SCANCODE_EQUALS: return '='; + case SDL_SCANCODE_LEFTBRACKET: return '['; + case SDL_SCANCODE_RIGHTBRACKET: return ']'; + case SDL_SCANCODE_BACKSLASH: return '\\'; + case SDL_SCANCODE_NONUSHASH: return '#'; + case SDL_SCANCODE_SEMICOLON: return ';'; + case SDL_SCANCODE_APOSTROPHE: return '\''; + case SDL_SCANCODE_GRAVE: return '`'; + case SDL_SCANCODE_COMMA: return ','; + case SDL_SCANCODE_PERIOD: return '.'; + case SDL_SCANCODE_SLASH: return '/'; + case SDL_SCANCODE_NONUSBACKSLASH: return '\\'; + + case SDL_SCANCODE_BACKSPACE: return K_BACKSPACE; + case SDL_SCANCODE_UP: return K_UPARROW; + case SDL_SCANCODE_DOWN: return K_DOWNARROW; + case SDL_SCANCODE_LEFT: return K_LEFTARROW; + case SDL_SCANCODE_RIGHT: return K_RIGHTARROW; + + case SDL_SCANCODE_LALT: return K_ALT; + case SDL_SCANCODE_RALT: return K_ALT; + case SDL_SCANCODE_LCTRL: return K_CTRL; + case SDL_SCANCODE_RCTRL: return K_CTRL; + case SDL_SCANCODE_LSHIFT: return K_SHIFT; + case SDL_SCANCODE_RSHIFT: return K_SHIFT; + + case SDL_SCANCODE_F1: return K_F1; + case SDL_SCANCODE_F2: return K_F2; + case SDL_SCANCODE_F3: return K_F3; + case SDL_SCANCODE_F4: return K_F4; + case SDL_SCANCODE_F5: return K_F5; + case SDL_SCANCODE_F6: return K_F6; + case SDL_SCANCODE_F7: return K_F7; + case SDL_SCANCODE_F8: return K_F8; + case SDL_SCANCODE_F9: return K_F9; + case SDL_SCANCODE_F10: return K_F10; + case SDL_SCANCODE_F11: return K_F11; + case SDL_SCANCODE_F12: return K_F12; + case SDL_SCANCODE_INSERT: return K_INS; + case SDL_SCANCODE_DELETE: return K_DEL; + case SDL_SCANCODE_PAGEDOWN: return K_PGDN; + case SDL_SCANCODE_PAGEUP: return K_PGUP; + case SDL_SCANCODE_HOME: return K_HOME; + case SDL_SCANCODE_END: return K_END; + + case SDL_SCANCODE_NUMLOCKCLEAR: return K_KP_NUMLOCK; + case SDL_SCANCODE_KP_DIVIDE: return K_KP_SLASH; + case SDL_SCANCODE_KP_MULTIPLY: return K_KP_STAR; + case SDL_SCANCODE_KP_MINUS: return K_KP_MINUS; + case SDL_SCANCODE_KP_7: return K_KP_HOME; + case SDL_SCANCODE_KP_8: return K_KP_UPARROW; + case SDL_SCANCODE_KP_9: return K_KP_PGUP; + case SDL_SCANCODE_KP_PLUS: return K_KP_PLUS; + case SDL_SCANCODE_KP_4: return K_KP_LEFTARROW; + case SDL_SCANCODE_KP_5: return K_KP_5; + case SDL_SCANCODE_KP_6: return K_KP_RIGHTARROW; + case SDL_SCANCODE_KP_1: return K_KP_END; + case SDL_SCANCODE_KP_2: return K_KP_DOWNARROW; + case SDL_SCANCODE_KP_3: return K_KP_PGDN; + case SDL_SCANCODE_KP_ENTER: return K_KP_ENTER; + case SDL_SCANCODE_KP_0: return K_KP_INS; + case SDL_SCANCODE_KP_PERIOD: return K_KP_DEL; + + case SDL_SCANCODE_LGUI: return K_COMMAND; + case SDL_SCANCODE_RGUI: return K_COMMAND; + + case SDL_SCANCODE_PAUSE: return K_PAUSE; + + default: return 0; + } +} + +static void IN_DebugTextEvent(SDL_Event *event) +{ + Con_Printf ("SDL_TEXTINPUT '%s' time: %g\n", event->text.text, Sys_DoubleTime()); +} + +static void IN_DebugKeyEvent(SDL_Event *event) +{ + const char *eventtype = (event->key.state == SDL_PRESSED) ? "SDL_KEYDOWN" : "SDL_KEYUP"; + Con_Printf ("%s scancode: '%s' keycode: '%s' time: %g\n", + eventtype, + SDL_GetScancodeName(event->key.keysym.scancode), + SDL_GetKeyName(event->key.keysym.sym), + Sys_DoubleTime()); +} + +void IN_SendKeyEvents (void) +{ + SDL_Event event; + int key; + qboolean down; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) + S_UnblockSound(); + else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) + S_BlockSound(); + break; + case SDL_TEXTINPUT: + if (in_debugkeys.value) + IN_DebugTextEvent(&event); + + // SDL2: We use SDL_TEXTINPUT for typing in the console / chat. + // SDL2 uses the local keyboard layout and handles modifiers + // (shift for uppercase, etc.) for us. + { + unsigned char *ch; + for (ch = (unsigned char *)event.text.text; *ch; ch++) + if ((*ch & ~0x7F) == 0) + Char_Event (*ch); + } + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + down = (event.key.state == SDL_PRESSED); + + if (in_debugkeys.value) + IN_DebugKeyEvent(&event); + + // SDL2: we interpret the keyboard as the US layout, so keybindings + // are based on key position, not the label on the key cap. + key = IN_SDL2_ScancodeToQuakeKey(event.key.keysym.scancode); + + Key_Event (key, down); + + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (event.button.button < 1 || + event.button.button > sizeof(buttonremap) / sizeof(buttonremap[0])) + { + Con_Printf ("Ignored event for mouse button %d\n", + event.button.button); + break; + } + Key_Event(buttonremap[event.button.button - 1], event.button.state == SDL_PRESSED); + break; + + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) + { + Key_Event(K_MWHEELUP, true); + Key_Event(K_MWHEELUP, false); + } + else if (event.wheel.y < 0) + { + Key_Event(K_MWHEELDOWN, true); + Key_Event(K_MWHEELDOWN, false); + } + break; + + case SDL_MOUSEMOTION: + IN_MouseMotion(event.motion.xrel, event.motion.yrel); + break; + + case SDL_CONTROLLERDEVICEADDED: + if (joy_active_instaceid == -1) + { + joy_active_controller = SDL_GameControllerOpen(event.cdevice.which); + if (joy_active_controller == NULL) + Con_DPrintf("Couldn't open game controller\n"); + else + { + SDL_Joystick *joy; + joy = SDL_GameControllerGetJoystick(joy_active_controller); + joy_active_instaceid = SDL_JoystickInstanceID(joy); + } + } + else + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEADDED\n"); + break; + case SDL_CONTROLLERDEVICEREMOVED: + if (joy_active_instaceid != -1 && event.cdevice.which == joy_active_instaceid) + { + SDL_GameControllerClose(joy_active_controller); + joy_active_controller = NULL; + joy_active_instaceid = -1; + } + else + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEREMOVED\n"); + break; + case SDL_CONTROLLERDEVICEREMAPPED: + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEREMAPPED\n"); + break; + + case SDL_QUIT: + CL_Disconnect (); + Sys_Quit (); + break; + + default: + break; + } + } +} + diff --git a/source/input.h b/source/input.h new file mode 100644 index 0000000..fe551fb --- /dev/null +++ b/source/input.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_INPUT_H +#define _QUAKE_INPUT_H + +// input.h -- external (non-keyboard) input devices + +void IN_Init (void); + +void IN_Shutdown (void); + +void IN_Commands (void); +// oportunity for devices to stick commands on the script buffer + +// mouse moved by dx and dy pixels +void IN_MouseMotion(int dx, int dy); + + +void IN_SendKeyEvents (void); +// used as a callback for Sys_SendKeyEvents() by some drivers + +void IN_UpdateInputMode (void); +// do stuff if input mode (text/non-text) changes matter to the keyboard driver + +void IN_Move (usercmd_t *cmd); +// add additional movement on top of the keyboard move cmd + +void IN_ClearStates (void); +// restores all button and position states to defaults + +// called when the app becomes active +void IN_Activate (); + +// called when the app becomes inactive +void IN_Deactivate (qboolean free_cursor); + +#endif /* _QUAKE_INPUT_H */ + diff --git a/source/keys.c b/source/keys.c new file mode 100644 index 0000000..15f77ed --- /dev/null +++ b/source/keys.c @@ -0,0 +1,1201 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "arch_def.h" + +/* key up events are sent even if in console mode */ + +#define HISTORY_FILE_NAME "history.txt" + +char key_lines[CMDLINES][MAXCMDLINE]; + +int key_linepos; +int key_insert; //johnfitz -- insert key toggle (for editing) +double key_blinktime; //johnfitz -- fudge cursor blinking to make it easier to spot in certain cases + +int edit_line = 0; +int history_line = 0; + +keydest_t key_dest; + +char *keybindings[MAX_KEYS]; +qboolean consolekeys[MAX_KEYS]; // if true, can't be rebound while in console +qboolean menubound[MAX_KEYS]; // if true, can't be rebound while in menu +qboolean keydown[MAX_KEYS]; + +typedef struct +{ + const char *name; + int keynum; +} keyname_t; + +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + +// {"KP_NUMLOCK", K_KP_NUMLOCK}, + {"KP_SLASH", K_KP_SLASH}, + {"KP_STAR", K_KP_STAR}, + {"KP_MINUS", K_KP_MINUS}, + {"KP_HOME", K_KP_HOME}, + {"KP_UPARROW", K_KP_UPARROW}, + {"KP_PGUP", K_KP_PGUP}, + {"KP_PLUS", K_KP_PLUS}, + {"KP_LEFTARROW", K_KP_LEFTARROW}, + {"KP_5", K_KP_5}, + {"KP_RIGHTARROW", K_KP_RIGHTARROW}, + {"KP_END", K_KP_END}, + {"KP_DOWNARROW", K_KP_DOWNARROW}, + {"KP_PGDN", K_KP_PGDN}, + {"KP_ENTER", K_KP_ENTER}, + {"KP_INS", K_KP_INS}, + {"KP_DEL", K_KP_DEL}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"COMMAND", K_COMMAND}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + {"AUX17", K_AUX17}, + {"AUX18", K_AUX18}, + {"AUX19", K_AUX19}, + {"AUX20", K_AUX20}, + {"AUX21", K_AUX21}, + {"AUX22", K_AUX22}, + {"AUX23", K_AUX23}, + {"AUX24", K_AUX24}, + {"AUX25", K_AUX25}, + {"AUX26", K_AUX26}, + {"AUX27", K_AUX27}, + {"AUX28", K_AUX28}, + {"AUX29", K_AUX29}, + {"AUX30", K_AUX30}, + {"AUX31", K_AUX31}, + {"AUX32", K_AUX32}, + + {"PAUSE", K_PAUSE}, + + {"MWHEELUP", K_MWHEELUP}, + {"MWHEELDOWN", K_MWHEELDOWN}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"BACKQUOTE", '`'}, // because a raw backquote may toggle the console + {"TILDE", '~'}, // because a raw tilde may toggle the console + + {"LTHUMB", K_LTHUMB}, + {"RTHUMB", K_RTHUMB}, + {"LSHOULDER", K_LSHOULDER}, + {"RSHOULDER", K_RSHOULDER}, + {"ABUTTON", K_ABUTTON}, + {"BBUTTON", K_BBUTTON}, + {"XBUTTON", K_XBUTTON}, + {"YBUTTON", K_YBUTTON}, + {"LTRIGGER", K_LTRIGGER}, + {"RTRIGGER", K_RTRIGGER}, + + {NULL, 0} +}; + +/* +============================================================================== + + LINE TYPING INTO THE CONSOLE + +============================================================================== +*/ + +static void PasteToConsole (void) +{ + char *cbd, *p, *workline; + int mvlen, inslen; + + if (key_linepos == MAXCMDLINE - 1) + return; + + if ((cbd = PL_GetClipboardData()) == NULL) + return; + + p = cbd; + while (*p) + { + if (*p == '\n' || *p == '\r' || *p == '\b') + { + *p = 0; + break; + } + p++; + } + + inslen = (int) (p - cbd); + if (inslen + key_linepos > MAXCMDLINE - 1) + inslen = MAXCMDLINE - 1 - key_linepos; + if (inslen <= 0) goto done; + + workline = key_lines[edit_line]; + workline += key_linepos; + mvlen = (int) strlen(workline); + if (mvlen + inslen + key_linepos > MAXCMDLINE - 1) + { + mvlen = MAXCMDLINE - 1 - key_linepos - inslen; + if (mvlen < 0) mvlen = 0; + } + + // insert the string + if (mvlen != 0) + memmove (workline + inslen, workline, mvlen); + memcpy (workline, cbd, inslen); + key_linepos += inslen; + workline[mvlen + inslen] = '\0'; + done: + Z_Free(cbd); +} + +/* +==================== +Key_Console -- johnfitz -- heavy revision + +Interactive line editing and console scrollback +==================== +*/ +extern char *con_text, key_tabpartial[MAXCMDLINE]; +extern int con_current, con_linewidth, con_vislines; + +void Key_Console (int key) +{ + static char current[MAXCMDLINE] = ""; + int history_line_last; + size_t len; + char *workline = key_lines[edit_line]; + + switch (key) + { + case K_ENTER: + case K_KP_ENTER: + key_tabpartial[0] = 0; + Cbuf_AddText (workline + 1); // skip the prompt + Cbuf_AddText ("\n"); + Con_Printf ("%s\n", workline); + + // If the last two lines are identical, skip storing this line in history + // by not incrementing edit_line + if (strcmp(workline, key_lines[(edit_line-1)&31])) + edit_line = (edit_line + 1) & 31; + + history_line = edit_line; + key_lines[edit_line][0] = ']'; + key_lines[edit_line][1] = 0; //johnfitz -- otherwise old history items show up in the new edit line + key_linepos = 1; + if (cls.state == ca_disconnected) + SCR_UpdateScreen (); // force an update, because the command may take some time + return; + + case K_TAB: + Con_TabComplete (); + return; + + case K_BACKSPACE: + key_tabpartial[0] = 0; + if (key_linepos > 1) + { + workline += key_linepos - 1; + if (workline[1]) + { + len = strlen(workline); + memmove (workline, workline + 1, len); + } + else *workline = 0; + key_linepos--; + } + return; + + case K_DEL: + key_tabpartial[0] = 0; + workline += key_linepos; + if (*workline) + { + if (workline[1]) + { + len = strlen(workline); + memmove (workline, workline + 1, len); + } + else *workline = 0; + } + return; + + case K_HOME: + if (keydown[K_CTRL]) + { + //skip initial empty lines + int i, x; + char *line; + + for (i = con_current - con_totallines + 1; i <= con_current; i++) + { + line = con_text + (i % con_totallines) * con_linewidth; + for (x = 0; x < con_linewidth; x++) + { + if (line[x] != ' ') + break; + } + if (x != con_linewidth) + break; + } + con_backscroll = CLAMP(0, con_current-i%con_totallines-2, con_totallines-(glheight>>3)-1); + } + else key_linepos = 1; + return; + + case K_END: + if (keydown[K_CTRL]) + con_backscroll = 0; + else key_linepos = strlen(workline); + return; + + case K_PGUP: + case K_MWHEELUP: + con_backscroll += keydown[K_CTRL] ? ((con_vislines>>3) - 4) : 2; + if (con_backscroll > con_totallines - (vid.height>>3) - 1) + con_backscroll = con_totallines - (vid.height>>3) - 1; + return; + + case K_PGDN: + case K_MWHEELDOWN: + con_backscroll -= keydown[K_CTRL] ? ((con_vislines>>3) - 4) : 2; + if (con_backscroll < 0) + con_backscroll = 0; + return; + + case K_LEFTARROW: + if (key_linepos > 1) + { + key_linepos--; + key_blinktime = realtime; + } + return; + + case K_RIGHTARROW: + len = strlen(workline); + if ((int)len == key_linepos) + { + len = strlen(key_lines[(edit_line + 31) & 31]); + if ((int)len <= key_linepos) + return; // no character to get + workline += key_linepos; + *workline = key_lines[(edit_line + 31) & 31][key_linepos]; + workline[1] = 0; + key_linepos++; + } + else + { + key_linepos++; + key_blinktime = realtime; + } + return; + + case K_UPARROW: + if (history_line == edit_line) + Q_strcpy(current, workline); + + history_line_last = history_line; + do + { + history_line = (history_line - 1) & 31; + } while (history_line != edit_line && !key_lines[history_line][1]); + + if (history_line == edit_line) + { + history_line = history_line_last; + return; + } + + key_tabpartial[0] = 0; + Q_strcpy(workline, key_lines[history_line]); + key_linepos = Q_strlen(workline); + return; + + case K_DOWNARROW: + if (history_line == edit_line) + return; + + key_tabpartial[0] = 0; + + do + { + history_line = (history_line + 1) & 31; + } while (history_line != edit_line && !key_lines[history_line][1]); + + if (history_line == edit_line) + Q_strcpy(workline, current); + else Q_strcpy(workline, key_lines[history_line]); + key_linepos = Q_strlen(workline); + return; + + case K_INS: + if (keydown[K_SHIFT]) /* Shift-Ins paste */ + PasteToConsole(); + else key_insert ^= 1; + return; + + case 'v': + case 'V': +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + if (keydown[K_COMMAND]) { /* Cmd+v paste (Mac-only) */ + PasteToConsole(); + return; + } +#endif + if (keydown[K_CTRL]) { /* Ctrl+v paste */ + PasteToConsole(); + return; + } + break; + + case 'c': + case 'C': + if (keydown[K_CTRL]) { /* Ctrl+C: abort the line -- S.A */ + Con_Printf ("%s\n", workline); + workline[0] = ']'; + workline[1] = 0; + key_linepos = 1; + history_line= edit_line; + return; + } + break; + } +} + +void Char_Console (int key) +{ + size_t len; + char *workline = key_lines[edit_line]; + + if (key_linepos < MAXCMDLINE-1) + { + qboolean endpos = !workline[key_linepos]; + + key_tabpartial[0] = 0; //johnfitz + // if inserting, move the text to the right + if (key_insert && !endpos) + { + workline[MAXCMDLINE - 2] = 0; + workline += key_linepos; + len = strlen(workline) + 1; + memmove (workline + 1, workline, len); + *workline = key; + } + else + { + workline += key_linepos; + *workline = key; + // null terminate if at the end + if (endpos) + workline[1] = 0; + } + key_linepos++; + } +} + +//============================================================================ + +qboolean chat_team = false; +static char chat_buffer[MAXCMDLINE]; +static int chat_bufferlen = 0; + +const char *Key_GetChatBuffer (void) +{ + return chat_buffer; +} + +int Key_GetChatMsgLen (void) +{ + return chat_bufferlen; +} + +void Key_EndChat (void) +{ + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; +} + +void Key_Message (int key) +{ + switch (key) + { + case K_ENTER: + case K_KP_ENTER: + if (chat_team) + Cbuf_AddText ("say_team \""); + else + Cbuf_AddText ("say \""); + Cbuf_AddText(chat_buffer); + Cbuf_AddText("\"\n"); + + Key_EndChat (); + return; + + case K_ESCAPE: + Key_EndChat (); + return; + + case K_BACKSPACE: + if (chat_bufferlen) + chat_buffer[--chat_bufferlen] = 0; + return; + } +} + +void Char_Message (int key) +{ + if (chat_bufferlen == sizeof(chat_buffer) - 1) + return; // all full + + chat_buffer[chat_bufferlen++] = key; + chat_buffer[chat_bufferlen] = 0; +} + +//============================================================================ + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keybindings[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. +=================== +*/ +int Key_StringToKeynum (const char *str) +{ + keyname_t *kn; + + if (!str || !str[0]) + return -1; + if (!str[1]) + return str[0]; + + for (kn=keynames ; kn->name ; kn++) + { + if (!q_strcasecmp(str,kn->name)) + return kn->keynum; + } + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, or a K_* name) for the +given keynum. +FIXME: handle quote special (general escape sequence?) +=================== +*/ +const char *Key_KeynumToString (int keynum) +{ + static char tinystr[2]; + keyname_t *kn; + + if (keynum == -1) + return ""; + if (keynum > 32 && keynum < 127) + { // printable ascii + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + for (kn = keynames; kn->name; kn++) + { + if (keynum == kn->keynum) + return kn->name; + } + + return ""; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding (int keynum, const char *binding) +{ + if (keynum == -1) + return; + +// free old bindings + if (keybindings[keynum]) + { + Z_Free (keybindings[keynum]); + keybindings[keynum] = NULL; + } + +// allocate memory for new binding + if (binding) + keybindings[keynum] = Z_Strdup(binding); +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Con_Printf ("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; + } + + Key_SetBinding (b, NULL); +} + +void Key_Unbindall_f (void) +{ + int i; + + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i]) + Key_SetBinding (i, NULL); + } +} + +/* +============ +Key_Bindlist_f -- johnfitz +============ +*/ +void Key_Bindlist_f (void) +{ + int i, count; + + count = 0; + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i] && *keybindings[i]) + { + Con_SafePrintf (" %s \"%s\"\n", Key_KeynumToString(i), keybindings[i]); + count++; + } + } + Con_SafePrintf ("%i bindings\n", count); +} + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c != 2 && c != 3) + { + Con_Printf ("bind [command] : attach a command to 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 (c == 2) + { + if (keybindings[b]) + Con_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] ); + else + Con_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; + for (i = 2; i < c; i++) + { + q_strlcat (cmd, Cmd_Argv(i), sizeof(cmd)); + if (i != (c-1)) + q_strlcat (cmd, " ", sizeof(cmd)); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings (FILE *f) +{ + int i; + + // unbindall before loading stored bindings: + if (cfg_unbindall.value) + fprintf (f, "unbindall\n"); + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i] && *keybindings[i]) + fprintf (f, "bind \"%s\" \"%s\"\n", Key_KeynumToString(i), keybindings[i]); + } +} + + +void History_Init (void) +{ + int i, c; + FILE *hf; + + for (i = 0; i < CMDLINES; i++) + { + key_lines[i][0] = ']'; + key_lines[i][1] = 0; + } + key_linepos = 1; + + hf = fopen(va("%s/%s", host_parms->userdir, HISTORY_FILE_NAME), "rt"); + if (hf != NULL) + { + do + { + i = 1; + do + { + c = fgetc(hf); + key_lines[edit_line][i++] = c; + } while (c != '\r' && c != '\n' && c != EOF && i < MAXCMDLINE); + key_lines[edit_line][i - 1] = 0; + edit_line = (edit_line + 1) & (CMDLINES - 1); + /* for people using a windows-generated history file on unix: */ + if (c == '\r' || c == '\n') + { + do + c = fgetc(hf); + while (c == '\r' || c == '\n'); + if (c != EOF) + ungetc(c, hf); + else c = 0; /* loop once more, otherwise last line is lost */ + } + } while (c != EOF && edit_line < CMDLINES); + fclose(hf); + + history_line = edit_line = (edit_line - 1) & (CMDLINES - 1); + key_lines[edit_line][0] = ']'; + key_lines[edit_line][1] = 0; + } +} + +void History_Shutdown (void) +{ + int i; + FILE *hf; + + hf = fopen(va("%s/%s", host_parms->userdir, HISTORY_FILE_NAME), "wt"); + if (hf != NULL) + { + i = edit_line; + do + { + i = (i + 1) & (CMDLINES - 1); + } while (i != edit_line && !key_lines[i][1]); + + while (i != edit_line && key_lines[i][1]) + { + fprintf(hf, "%s\n", key_lines[i] + 1); + i = (i + 1) & (CMDLINES - 1); + } + fclose(hf); + } +} + +/* +=================== +Key_Init +=================== +*/ +void Key_Init (void) +{ + int i; + + History_Init (); + + key_blinktime = realtime; //johnfitz + +// +// initialize consolekeys[] +// + for (i = 32; i < 127; i++) // ascii characters + consolekeys[i] = true; + consolekeys['`'] = false; + consolekeys['~'] = false; + consolekeys[K_TAB] = true; + consolekeys[K_ENTER] = true; + consolekeys[K_ESCAPE] = true; + consolekeys[K_BACKSPACE] = true; + consolekeys[K_UPARROW] = true; + consolekeys[K_DOWNARROW] = true; + consolekeys[K_LEFTARROW] = true; + consolekeys[K_RIGHTARROW] = true; + consolekeys[K_CTRL] = true; + consolekeys[K_SHIFT] = true; + consolekeys[K_INS] = true; + consolekeys[K_DEL] = true; + consolekeys[K_PGDN] = true; + consolekeys[K_PGUP] = true; + consolekeys[K_HOME] = true; + consolekeys[K_END] = true; + consolekeys[K_KP_NUMLOCK] = true; + consolekeys[K_KP_SLASH] = true; + consolekeys[K_KP_STAR] = true; + consolekeys[K_KP_MINUS] = true; + consolekeys[K_KP_HOME] = true; + consolekeys[K_KP_UPARROW] = true; + consolekeys[K_KP_PGUP] = true; + consolekeys[K_KP_PLUS] = true; + consolekeys[K_KP_LEFTARROW] = true; + consolekeys[K_KP_5] = true; + consolekeys[K_KP_RIGHTARROW] = true; + consolekeys[K_KP_END] = true; + consolekeys[K_KP_DOWNARROW] = true; + consolekeys[K_KP_PGDN] = true; + consolekeys[K_KP_ENTER] = true; + consolekeys[K_KP_INS] = true; + consolekeys[K_KP_DEL] = true; +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + consolekeys[K_COMMAND] = true; +#endif + consolekeys[K_MWHEELUP] = true; + consolekeys[K_MWHEELDOWN] = true; + +// +// initialize menubound[] +// + menubound[K_ESCAPE] = true; + for (i = 0; i < 12; i++) + menubound[K_F1+i] = true; + +// +// register our functions +// + Cmd_AddCommand ("bindlist",Key_Bindlist_f); //johnfitz + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); +} + +static struct { + qboolean active; + int lastkey; + int lastchar; +} key_inputgrab = { false, -1, -1 }; + +/* +=================== +Key_BeginInputGrab +=================== +*/ +void Key_BeginInputGrab (void) +{ + Key_ClearStates (); + + key_inputgrab.active = true; + key_inputgrab.lastkey = -1; + key_inputgrab.lastchar = -1; + + IN_UpdateInputMode (); +} + +/* +=================== +Key_EndInputGrab +=================== +*/ +void Key_EndInputGrab (void) +{ + Key_ClearStates (); + + key_inputgrab.active = false; + + IN_UpdateInputMode (); +} + +/* +=================== +Key_GetGrabbedInput +=================== +*/ +void Key_GetGrabbedInput (int *lastkey, int *lastchar) +{ + if (lastkey) + *lastkey = key_inputgrab.lastkey; + if (lastchar) + *lastchar = key_inputgrab.lastchar; +} + +/* +=================== +Key_Event + +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +=================== +*/ +void Key_Event (int key, qboolean down) +{ + char *kb; + char cmd[1024]; + + if (key < 0 || key >= MAX_KEYS) + return; + +// handle fullscreen toggle + if (down && (key == K_ENTER || key == K_KP_ENTER) && keydown[K_ALT]) + { + VID_Toggle(); + return; + } + +// handle autorepeats and stray key up events + if (down) + { + if (keydown[key]) + { + if (key_dest == key_game && !con_forcedup) + return; // ignore autorepeats in game mode + } + else if (key >= 200 && !keybindings[key]) + Con_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString(key)); + } + else if (!keydown[key]) + return; // ignore stray key up events + + keydown[key] = down; + + if (key_inputgrab.active) + { + if (down) + key_inputgrab.lastkey = key; + return; + } + +// handle escape specialy, so the user can never unbind it + if (key == K_ESCAPE) + { + if (!down) + return; + + if (keydown[K_SHIFT]) + { + Con_ToggleConsole_f(); + return; + } + + switch (key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + case key_game: + case key_console: + M_ToggleMenu_f (); + break; + default: + Sys_Error ("Bad key_dest"); + } + + return; + } + +// key up events only generate commands if the game key binding is +// a button command (leading + sign). These will occur even in console mode, +// to keep the character from continuing an action started before a console +// switch. Button commands include the kenum as a parameter, so multiple +// downs can be matched with ups + if (!down) + { + kb = keybindings[key]; + if (kb && kb[0] == '+') + { + sprintf (cmd, "-%s %i\n", kb+1, key); + Cbuf_AddText (cmd); + } + return; + } + +// during demo playback, most keys bring up the main menu + if (cls.demoplayback && down && consolekeys[key] && key_dest == key_game && key != K_TAB) + { + M_ToggleMenu_f (); + return; + } + +// if not a consolekey, send to the interpreter no matter what mode is + if ((key_dest == key_menu && menubound[key]) || + (key_dest == key_console && !consolekeys[key]) || + (key_dest == key_game && (!con_forcedup || !consolekeys[key]))) + { + kb = keybindings[key]; + if (kb) + { + if (kb[0] == '+') + { // button commands add keynum as a parm + sprintf (cmd, "%s %i\n", kb, key); + Cbuf_AddText (cmd); + } + else + { + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + } + return; + } + + if (!down) + return; // other systems only care about key down events + + switch (key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + + case key_game: + case key_console: + Key_Console (key); + break; + default: + Sys_Error ("Bad key_dest"); + } +} + +/* +=================== +Char_Event + +Called by the backend when the user has input a character. +=================== +*/ +void Char_Event (int key) +{ + if (key < 32 || key > 126) + return; + +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + if (keydown[K_COMMAND]) + return; +#endif + if (keydown[K_CTRL]) + return; + + if (key_inputgrab.active) + { + key_inputgrab.lastchar = key; + return; + } + + switch (key_dest) + { + case key_message: + Char_Message (key); + break; + case key_menu: + M_Charinput (key); + break; + case key_game: + if (!con_forcedup) + break; + /* fallthrough */ + case key_console: + Char_Console (key); + break; + default: + break; + } +} + +/* +=================== +Key_TextEntry +=================== +*/ +qboolean Key_TextEntry (void) +{ + if (key_inputgrab.active) + return true; + + switch (key_dest) + { + case key_message: + return true; + case key_menu: + return M_TextEntry(); + case key_game: + if (!con_forcedup) + return false; + /* fallthrough */ + case key_console: + return true; + default: + return false; + } +} + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + for (i = 0; i < MAX_KEYS; i++) + { + if (keydown[i]) + Key_Event (i, false); + } +} + +/* +=================== +Key_UpdateForDest +=================== +*/ +void Key_UpdateForDest (void) +{ + static qboolean forced = false; + + if (cls.state == ca_dedicated) + return; + + switch (key_dest) + { + case key_console: + if (forced && cls.state == ca_connected) + { + forced = false; + IN_Activate(); + key_dest = key_game; + } + break; + case key_game: + if (cls.state != ca_connected) + { + forced = true; + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + break; + } + /* fallthrough */ + default: + forced = false; + break; + } +} + diff --git a/source/keys.h b/source/keys.h new file mode 100644 index 0000000..2e178ef --- /dev/null +++ b/source/keys.h @@ -0,0 +1,200 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_KEYS_H +#define _QUAKE_KEYS_H + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_NUMLOCK 153 +#define K_KP_SLASH 154 +#define K_KP_STAR 155 +#define K_KP_MINUS 156 +#define K_KP_HOME 157 +#define K_KP_UPARROW 158 +#define K_KP_PGUP 159 +#define K_KP_PLUS 160 +#define K_KP_LEFTARROW 161 +#define K_KP_5 162 +#define K_KP_RIGHTARROW 163 +#define K_KP_END 164 +#define K_KP_DOWNARROW 165 +#define K_KP_PGDN 166 +#define K_KP_ENTER 167 +#define K_KP_INS 168 +#define K_KP_DEL 169 + +#define K_COMMAND 170 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 200 +#define K_MOUSE2 201 +#define K_MOUSE3 202 + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// aux29-32: reserved for the HAT (POV) switch motion +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 + +// JACK: Intellimouse(c) Mouse Wheel Support + +#define K_MWHEELUP 239 +#define K_MWHEELDOWN 240 + +// thumb buttons +#define K_MOUSE4 241 +#define K_MOUSE5 242 + +// SDL2 game controller keys +#define K_LTHUMB 243 +#define K_RTHUMB 244 +#define K_LSHOULDER 245 +#define K_RSHOULDER 246 +#define K_ABUTTON 247 +#define K_BBUTTON 248 +#define K_XBUTTON 249 +#define K_YBUTTON 250 +#define K_LTRIGGER 251 +#define K_RTRIGGER 252 + +#define MAX_KEYS 256 + +#define MAXCMDLINE 256 + +typedef enum {key_game, key_console, key_message, key_menu} keydest_t; + +extern keydest_t key_dest; +extern char *keybindings[MAX_KEYS]; + +#define CMDLINES 64 + +extern char key_lines[CMDLINES][MAXCMDLINE]; +extern int edit_line; +extern int key_linepos; +extern int key_insert; +extern double key_blinktime; + +extern qboolean chat_team; + +void Key_Init (void); +void Key_ClearStates (void); +void Key_UpdateForDest (void); + +void Key_BeginInputGrab (void); +void Key_EndInputGrab (void); +void Key_GetGrabbedInput (int *lastkey, int *lastchar); + +void Key_Event (int key, qboolean down); +void Char_Event (int key); +qboolean Key_TextEntry (void); + +void Key_SetBinding (int keynum, const char *binding); +const char *Key_KeynumToString (int keynum); +void Key_WriteBindings (FILE *f); + +void Key_EndChat (void); +const char *Key_GetChatBuffer (void); +int Key_GetChatMsgLen (void); + +void History_Init (void); +void History_Shutdown (void); + +#endif /* _QUAKE_KEYS_H */ + diff --git a/source/lodepng.c b/source/lodepng.c new file mode 100644 index 0000000..05c9fa2 --- /dev/null +++ b/source/lodepng.c @@ -0,0 +1,6248 @@ +/* +LodePNG version 20180326 + +Copyright (c) 2005-2018 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#include +#include +#include + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20180326"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) +{ + return malloc(size); +} + +static void* lodepng_realloc(void* ptr, size_t new_size) +{ + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) +{ + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code)\ +{\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code)\ +{\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call)\ +{\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code)\ +{\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*dynamic vector of unsigned ints*/ +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_reserve(uivector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) +{ + if(!uivector_reserve(p, size * sizeof(unsigned))) return 0; + p->size = size; + return 1; /*success*/ +} + +/*resize and give all new elements the value*/ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; ++i) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if(!ucvector_reserve(p, size * sizeof(unsigned char))) return 0; + p->size = size; + return 1; /*success*/ +} + +#ifdef LODEPNG_COMPILE_PNG + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + lodepng_free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa. If you use +init_buffer to take over a buffer and size, it is not needed to use cleanup*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER) +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned string_resize(char** out, size_t size) +{ + char* data = (char*)lodepng_realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +/*init a {char*, size_t} pair for use as string*/ +static void string_init(char** out) +{ + *out = NULL; + string_resize(out, 0); +} + +/*free the above pair again*/ +static void string_cleanup(char** out) +{ + lodepng_free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i; + if(string_resize(out, insize)) + { + for(i = 0; i != insize; ++i) + { + (*out)[i] = in[i]; + } + } +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned lodepng_read32bitInt(const unsigned char* buffer) +{ + return (unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); +} + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +#ifdef LODEPNG_COMPILE_ENCODER +static void lodepng_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ + lodepng_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) +{ + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) + { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) +{ + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if (readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) +{ + long size = lodepng_filesize(filename); + if (size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*TODO: this ignores potential out of memory errors*/ +#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit)\ +{\ + /*add a new byte at the end*/\ + if(((*bitpointer) & 7) == 0) ucvector_push_back(bitstream, (unsigned char)0);\ + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/\ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7));\ + ++(*bitpointer);\ +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) + +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i != nbits; ++i) + { + result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; + ++(*bitpointer); + } + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored, out of this +the huffman tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree +{ + unsigned* tree2d; + unsigned* tree1d; + unsigned* lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes to draw the tree in ascii art with C++*/ +/* +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i != tree->tree1d.size; ++i) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + tree->tree2d = 0; + tree->tree1d = 0; + tree->lengths = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + lodepng_free(tree->tree2d); + lodepng_free(tree->tree1d); + lodepng_free(tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + tree->tree2d = (unsigned*)lodepng_malloc(tree->numcodes * 2 * sizeof(unsigned)); + if(!tree->tree2d) return 83; /*alloc fail*/ + + /* + convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means + uninited, a value >= numcodes is an address to another bit, a value < numcodes + is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as + many columns as codes - 1. + A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + Here, the internal nodes are stored (what their 0 and 1 option point to). + There is only memory for such good tree currently, if there are more nodes + (due to too long length codes), error 55 will happen + */ + for(n = 0; n < tree->numcodes * 2; ++n) + { + tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + } + + for(n = 0; n < tree->numcodes; ++n) /*the codes*/ + { + for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1); + /*oversubscribed, see comment in lodepng_error_text*/ + if(treepos > 2147483647 || treepos + 2 > tree->numcodes) return 55; + if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths[n]) /*last bit*/ + { + tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else + { + /*put address of the next step in here, first that address has to be found of course + (it's just nodefilled + 1)...*/ + ++nodefilled; + /*addresses encoded with numcodes added to it*/ + tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes; + treepos = nodefilled; + } + } + else treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes; + } + } + + for(n = 0; n < tree->numcodes * 2; ++n) + { + if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/ + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + uivector blcount; + uivector nextcode; + unsigned error = 0; + unsigned bits, n; + + uivector_init(&blcount); + uivector_init(&nextcode); + + tree->tree1d = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + if(!tree->tree1d) error = 83; /*alloc fail*/ + + if(!uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 83; /*alloc fail*/ + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount.data[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) + { + nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) + { + if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++; + } + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode +{ + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists +{ + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) +{ + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) + { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) + { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) + { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) +{ + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) + { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) + { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) + { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) +{ + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) + { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } + else + { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) + { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) + { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) + { + if(frequencies[i] > 0) + { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + for(i = 0; i != numcodes; ++i) lengths[i] = 0; + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) + { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } + else if(numpresent == 1) + { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } + else + { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) + { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) + { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) + { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->lengths = (unsigned*)lodepng_realloc(tree->lengths, numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + /*initialize all lengths to 0*/ + memset(tree->lengths, 0, numcodes * sizeof(unsigned)); + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) +{ + return tree->tree1d[index]; +} + +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) +{ + return tree->lengths[index]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code, or (unsigned)(-1) if error happened +inbitlength is the length of the complete buffer, in bits (so its byte length times 8) +*/ +static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, + const HuffmanTree* codetree, size_t inbitlength) +{ + unsigned treepos = 0, ct; + for(;;) + { + if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ + /* + decode the symbol from the tree. The "readBitFromStream" code is inlined in + the expression below because this is the biggest bottleneck while decoding + */ + ct = codetree->tree2d[(treepos << 1) + READBIT(*bp, in)]; + ++(*bp); + if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/ + else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/ + + if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/ + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + /*TODO: check for out of memory errors*/ + generateFixedLitLenTree(tree_ll); + generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + size_t inbitlength = inlength * 8; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if((*bp) + 14 > (inlength << 3)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBitsFromStream(bp, in, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; + + if((*bp) + HCLEN * 3 > (inlength << 3)) return 50; /*error: the bit pointer is or will go past the memory*/ + + HuffmanTree_init(&tree_cl); + + while(!error) + { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/); + + for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i) + { + if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); + else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != NUM_DEFLATE_CODE_SYMBOLS; ++i) bitlen_ll[i] = 0; + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen_d[i] = 0; + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) + { + unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + if((*bp + 2) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if((*bp + 3) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if((*bp + 7) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + if(code == (unsigned)(-1)) + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inbitlength ? 10 : 11; + } + else error = 16; /*unexisting code, this can never happen*/ + break; + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, + size_t* pos, size_t inlength, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + size_t inbitlength = inlength * 8; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); + else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ + { + /*code_ll is literal, length or end code*/ + unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); + if(code_ll <= 255) /*literal symbol*/ + { + /*ucvector_push_back would do the same, but for some reason the two lines below run 10% faster*/ + if(!ucvector_resize(out, (*pos) + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[*pos] = (unsigned char)code_ll; + ++(*pos); + } + else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, forward, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if((*bp + numextrabits_l) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits_l); + + /*part 3: get distance code*/ + code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); + if(code_d > 29) + { + if(code_d == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 18; /*error: invalid distance code (30-31 are never used)*/ + break; + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if((*bp + numextrabits_d) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + distance += readBitsFromStream(bp, in, numextrabits_d); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, (*pos) + length)) ERROR_BREAK(83 /*alloc fail*/); + if (distance < length) { + for(forward = 0; forward < length; ++forward) + { + out->data[(*pos)++] = out->data[backward++]; + } + } else { + memcpy(out->data + *pos, out->data + backward, length); + *pos += length; + } + } + else if(code_ll == 256) + { + break; /*end code, break the loop*/ + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = ((*bp) > inlength * 8) ? 10 : 11; + break; + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + size_t p; + unsigned LEN, NLEN, n, error = 0; + + /*go to first boundary of byte*/ + while(((*bp) & 0x7) != 0) ++(*bp); + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p + 4 >= inlength) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256u * in[p + 1]; p += 2; + NLEN = in[p] + 256u * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if(!ucvector_resize(out, (*pos) + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; ++n) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + size_t bp = 0; + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + unsigned error = 0; + + (void)settings; + + while(!BFINAL) + { + unsigned BTYPE; + if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, in); + BTYPE = 1u * readBitFromStream(&bp, in); + BTYPE += 2u * readBitFromStream(&bp, in); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, in, &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, in, &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + if(settings->custom_inflate) + { + return settings->custom_inflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_inflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if (array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash +{ + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) +{ + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) + { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) +{ + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) +{ + unsigned result = 0; + if(pos + 2 < size) + { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= (unsigned)(data[pos + 0] << 0u); + result ^= (unsigned)(data[pos + 1] << 4u); + result ^= (unsigned)(data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= (unsigned)(data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) +{ + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) +{ + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) +{ + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) + { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) + { + if(chainlength++ >= maxchainlength) break; + current_offset = hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize; + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) + { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) + { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ + { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) + { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } + else + { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) + { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) + { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) + { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else if(length < minmatch || (length == 3 && offset > 4096)) + { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) + { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = (datasize + 65534) / 65535; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN & 255)); + ucvector_push_back(out, (unsigned char)(LEN >> 8)); + ucvector_push_back(out, (unsigned char)(NLEN & 255)); + ucvector_push_back(out, (unsigned char)(NLEN >> 8)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; ++j) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) +{ + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code), + HuffmanTree_getLength(tree_d, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lenghts used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + uivector frequencies_ll; /*frequency of lit,len codes*/ + uivector frequencies_d; /*frequency of dist codes*/ + uivector frequencies_cl; /*frequency of code length codes*/ + uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/ + uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/ + /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl + (these are written as is in the file, it would be crazy to compress these using yet another huffman + tree that needs to be represented by yet another set of code lengths)*/ + uivector bitlen_cl; + size_t datasize = dataend - datapos; + + /* + Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t numcodes_ll, numcodes_d, i; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + uivector_init(&frequencies_ll); + uivector_init(&frequencies_d); + uivector_init(&frequencies_cl); + uivector_init(&bitlen_lld); + uivector_init(&bitlen_lld_e); + uivector_init(&bitlen_cl); + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) + { + if(settings->use_lz77) + { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83 /*alloc fail*/); + if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/); + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) + { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll.data[symbol]; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d.data[dist]; + i += 3; + } + } + frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, 257, frequencies_ll.size, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, 2, frequencies_d.size, 15); + if(error) break; + + numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286; + numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30; + /*store the code lengths of both generated trees in bitlen_lld*/ + for(i = 0; i != numcodes_ll; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i)); + for(i = 0; i != numcodes_d; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i)); + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != (unsigned)bitlen_lld.size; ++i) + { + unsigned j = 0; /*amount of repititions*/ + while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) ++j; + + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ + { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ + { + uivector_push_back(&bitlen_lld_e, 17); + uivector_push_back(&bitlen_lld_e, j - 3); + } + else /*repeat code 18 supports max 138 zeroes*/ + { + if(j > 138) j = 138; + uivector_push_back(&bitlen_lld_e, 18); + uivector_push_back(&bitlen_lld_e, j - 11); + } + i += (j - 1); + } + else if(j >= 3) /*repeat code for value other than zero*/ + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + for(k = 0; k < num; ++k) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, 6 - 3); + } + if(rest >= 3) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, rest - 3); + } + else j -= rest; + i += j; + } + else /*too short to benefit from repeat code*/ + { + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + + if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != bitlen_lld_e.size; ++i) + { + ++frequencies_cl.data[bitlen_lld_e.data[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e.data[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data, + frequencies_cl.size, frequencies_cl.size, 7); + if(error) break; + + if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != tree_cl.numcodes; ++i) + { + /*lenghts of code length tree is in the order as specified by deflate*/ + bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); + } + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) + { + /*remove zeros at the end, but minimum size must be 4*/ + if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/); + } + if(error) break; + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)bitlen_cl.size - 4; + /*trim zeroes for HCLEN. HLIT and HDIST were already trimmed at tree creation*/ + while(!bitlen_cl.data[HCLEN + 4 - 1] && HCLEN > 0) --HCLEN; + addBitsToStream(bp, out, HLIT, 5); + addBitsToStream(bp, out, HDIST, 5); + addBitsToStream(bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i != HCLEN + 4; ++i) addBitsToStream(bp, out, bitlen_cl.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i != bitlen_lld_e.size; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), + HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); + /*extra bits of repeat codes*/ + if(bitlen_lld_e.data[i] == 16) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 2); + else if(bitlen_lld_e.data[i] == 17) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 3); + else if(bitlen_lld_e.data[i] == 18) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64); + + /*write the end code*/ + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + uivector_cleanup(&frequencies_ll); + uivector_cleanup(&frequencies_d); + uivector_cleanup(&frequencies_cl); + uivector_cleanup(&bitlen_lld_e); + uivector_cleanup(&bitlen_lld); + uivector_cleanup(&bitlen_cl); + + return error; +} + +static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + generateFixedLitLenTree(&tree_ll); + generateFixedDistanceTree(&tree_d); + + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(bp, out, 0); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = datapos; i < dataend; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); + } + } + /*add END code*/ + if(!error) addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + size_t bp = 0; /*the bit pointer*/ + Hash hash; + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ + { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8 + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + if(error) return error; + + for(i = 0; i != numdeflateblocks && !error; ++i) + { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(out, &bp, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(out, &bp, &hash, in, start, end, settings, final); + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + if(settings->custom_deflate) + { + return settings->custom_deflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552 ? 5552 : len; + len -= amount; + while(amount > 0) + { + s1 += (*data++); + s2 += s1; + --amount; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) + { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) + { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) + { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflate(out, outsize, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) + { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(*out, (unsigned)(*outsize)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_decompress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out + that's pointing to a non allocated buffer, this'll crash*/ + ucvector outv; + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + + ucvector_push_back(&outv, (unsigned char)(CMFFLG >> 8)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG & 255)); + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + if(!error) + { + unsigned ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i != deflatesize; ++i) ucvector_push_back(&outv, deflatedata[i]); + lodepng_free(deflatedata); + lodepng_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) +{ + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) +{ + settings->ignore_adler32 = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) +{ + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) + { + r = lodepng_crc32_table[(r ^ data[i]) & 0xff] ^ (r >> 8); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) + { + result <<= 1; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) + { + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + } + ++(*bitpointer); +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) +{ + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) +{ + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) +{ + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) +{ + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) +{ + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) +{ + unsigned i; + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data) +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + lodepng_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = (unsigned char)type[0]; + chunk[5] = (unsigned char)type[1]; + chunk[6] = (unsigned char)type[2]; + chunk[7] = (unsigned char)type[3]; + + /*3: the data*/ + for(i = 0; i != length; ++i) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ +{ + switch(colortype) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) +{ + switch(colortype) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) +{ + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) +{ + size_t i; + lodepng_color_mode_cleanup(dest); + *dest = *source; + if(source->palette) + { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + for(i = 0; i != source->palettesize * 4; ++i) dest->palette[i] = source->palette[i]; + } + return 0; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) +{ + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) + { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) + { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) +{ + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with + the max of 256 colors, it'll have the exact alloc size*/ + if(!info->palette) /*allocate palette if empty*/ + { + /*room for 256 colors with 4 bytes each*/ + data = (unsigned char*)lodepng_realloc(info->palette, 1024); + if(!data) return 83; /*alloc fail*/ + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +unsigned lodepng_get_bpp(const LodePNGColorMode* info) +{ + /*calculate bits per pixel out of colortype and bitdepth*/ + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) +{ + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) +{ + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) +{ + size_t i; + for(i = 0; i != info->palettesize; ++i) + { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) +{ + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp(color); + size_t n = w * h; + return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8; +} + + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_DECODER +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer*/ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp(color); + size_t line = ((w / 8) * bpp) + ((w & 7) * bpp + 7) / 8; + return h * line; +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) +{ + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) + { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) + { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) +{ + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->text_num; ++i) + { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +void lodepng_clear_text(LodePNGInfo* info) +{ + LodePNGText_cleanup(info); +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + if(!new_keys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->text_num; + info->text_keys = new_keys; + info->text_strings = new_strings; + + string_init(&info->text_keys[info->text_num - 1]); + string_set(&info->text_keys[info->text_num - 1], key); + + string_init(&info->text_strings[info->text_num - 1]); + string_set(&info->text_strings[info->text_num - 1], str); + + return 0; +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) +{ + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->itext_num; ++i) + { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) +{ + LodePNGIText_cleanup(info); +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_langtags); + lodepng_free(new_transkeys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->itext_num; + info->itext_keys = new_keys; + info->itext_langtags = new_langtags; + info->itext_transkeys = new_transkeys; + info->itext_strings = new_strings; + + string_init(&info->itext_keys[info->itext_num - 1]); + string_set(&info->itext_keys[info->itext_num - 1], key); + + string_init(&info->itext_langtags[info->itext_num - 1]); + string_set(&info->itext_langtags[info->itext_num - 1], langtag); + + string_init(&info->itext_transkeys[info->itext_num - 1]); + string_set(&info->itext_transkeys[info->itext_num - 1], transkey); + + string_init(&info->itext_strings[info->itext_num - 1]); + string_set(&info->itext_strings[info->itext_num - 1], str); + + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) +{ + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) +{ + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + lodepng_info_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) +{ + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8] = in; + else out[index * bits / 8] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree +{ + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) tree->children[i] = 0; + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) + { + if(tree->children[i]) + { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int bit = 0; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/ +static void color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) +{ + int bit; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) + { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) out[i] = grey; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey; + else + { + /*take the most significant bits of grey*/ + grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1); + addColorBits(out, i, mode->bitdepth, grey); + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + else + { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } + else if(mode->colortype == LCT_PALETTE) + { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) + { + out[i * 2 + 0] = grey; + out[i * 2 + 1] = a; + } + else if(mode->bitdepth == 16) + { + out[i * 4 + 0] = out[i * 4 + 1] = grey; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } + else + { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 2 + 0] = (grey >> 8) & 255; + out[i * 2 + 1] = grey & 255; + } + else if(mode->colortype == LCT_RGB) + { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 4 + 0] = (grey >> 8) & 255; + out[i * 4 + 1] = grey & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } + else if(mode->colortype == LCT_RGBA) + { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } + else if(mode->bitdepth == 16) + { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } + else + { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else + { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but common PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + *r = *g = *b = 0; + *a = 255; + } + else + { + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } + else + { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } + else + { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to RGBA or RGB with 8 bit per cannel. buffer must be RGBA or RGB output with +enough memory, if has_alpha is true the output is RGBA. mode has the color mode +of the input buffer.*/ +static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, + unsigned has_alpha, const unsigned char* in, + const LodePNGColorMode* mode) +{ + unsigned num_channels = has_alpha ? 4 : 3; + size_t i; + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i]; + if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255; + } + } + else if(mode->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 3 + 0]; + buffer[1] = in[i * 3 + 1]; + buffer[2] = in[i * 3 + 2]; + if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r + && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + if(has_alpha) buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + if(mode->bitdepth == 8) index = in[i]; + else index = readBitsFromReversedStream(&j, in, mode->bitdepth); + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but most PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + buffer[0] = buffer[1] = buffer[2] = 0; + if(has_alpha) buffer[3] = 255; + } + else + { + buffer[0] = mode->palette[index * 4 + 0]; + buffer[1] = mode->palette[index * 4 + 1]; + buffer[2] = mode->palette[index * 4 + 2]; + if(has_alpha) buffer[3] = mode->palette[index * 4 + 3]; + } + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + if(has_alpha) buffer[3] = in[i * 2 + 1]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + if(has_alpha) buffer[3] = in[i * 4 + 2]; + } + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 4 + 0]; + buffer[1] = in[i * 4 + 1]; + buffer[2] = in[i * 4 + 2]; + if(has_alpha) buffer[3] = in[i * 4 + 3]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + if(has_alpha) buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_RGB) + { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } + else if(mode->colortype == LCT_RGBA) + { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) +{ + size_t i; + ColorTree tree; + size_t numpixels = w * h; + unsigned error = 0; + + if(lodepng_color_mode_equal(mode_out, mode_in)) + { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + for(i = 0; i != numbytes; ++i) out[i] = in[i]; + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) + { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) + { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if (mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) + { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + for(i = 0; i != numbytes; ++i) out[i] = in[i]; + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) + { + const unsigned char* p = &palette[i * 4]; + color_tree_add(&tree, p[0], p[1], p[2], p[3], i); + } + } + + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) + { + getPixelColorsRGBA8(out, numpixels, 1, in, mode_in); + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) + { + getPixelColorsRGBA8(out, numpixels, 0, in, mode_in); + } + else + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if (error) break; + } + } + + if(mode_out->colortype == LCT_PALETTE) + { + color_tree_cleanup(&tree); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_profile_init(LodePNGColorProfile* profile) +{ + profile->colored = 0; + profile->key = 0; + profile->key_r = profile->key_g = profile->key_b = 0; + profile->alpha = 0; + profile->numcolors = 0; + profile->bits = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorProfile(LodePNGColorProfile* p) +{ + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) +{ + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*profile must already have been inited with mode. +It's ok to set some parameters of profile to done already.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode) +{ + unsigned error = 0; + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + unsigned colored_done = lodepng_is_greyscale_type(mode) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode); + unsigned bits_done = bpp == 1 ? 1 : 0; + unsigned maxnumcolors = 257; + unsigned sixteen = 0; + if(bpp <= 8) maxnumcolors = bpp == 1 ? 2 : (bpp == 2 ? 4 : (bpp == 4 ? 16 : 256)); + + color_tree_init(&tree); + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode->bitdepth == 16) + { + unsigned short r, g, b, a; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ + { + sixteen = 1; + break; + } + } + } + + if(sixteen) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + profile->bits = 16; + bits_done = numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 65535 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 65535 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(profile->key && !profile->alpha) + { + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + } + } + } + } + else /* < 16-bit */ + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + + if(!bits_done && profile->bits < 8) + { + /*only r is checked, < 8 bits is only relevant for greyscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > profile->bits) profile->bits = bits; + } + bits_done = (profile->bits >= bpp); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 255 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 255 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) + { + if(!color_tree_has(&tree, r, g, b, a)) + { + color_tree_add(&tree, r, g, b, a, profile->numcolors); + if(profile->numcolors < 256) + { + unsigned char* p = profile->palette; + unsigned n = profile->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++profile->numcolors; + numcolors_done = profile->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(profile->key && !profile->alpha) + { + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + profile->key = 0; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the profile's key always 16-bit for consistency - repeat each byte twice*/ + profile->key_r += (profile->key_r << 8); + profile->key_g += (profile->key_g << 8); + profile->key_b += (profile->key_b << 8); + } + + color_tree_cleanup(&tree); + return error; +} + +/*Automatically chooses color type that gives smallest amount of bits in the +output image, e.g. grey if there are only greyscale pixels, palette if there +are less than 256 colors, ... +Updates values of mode with a potentially smaller color model. mode_out should +contain the user chosen color model, but will be overwritten with the new chosen one.*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) +{ + LodePNGColorProfile prof; + unsigned error = 0; + unsigned i, n, palettebits, palette_ok; + + lodepng_color_profile_init(&prof); + error = lodepng_get_color_profile(&prof, image, w, h, mode_in); + if(error) return error; + mode_out->key_defined = 0; + + if(prof.key && w * h <= 16) + { + prof.alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + prof.key = 0; + if(prof.bits < 8) prof.bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + n = prof.numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && prof.bits <= 8; + if(w * h < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(!prof.colored && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ + + if(palette_ok) + { + unsigned char* p = prof.palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != prof.numcolors; ++i) + { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) + { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } + else /*8-bit or 16-bit per channel*/ + { + mode_out->bitdepth = prof.bits; + mode_out->colortype = prof.alpha ? (prof.colored ? LCT_RGBA : LCT_GREY_ALPHA) + : (prof.colored ? LCT_RGB : LCT_GREY); + + if(prof.key) + { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/ + mode_out->key_r = prof.key_r & mask; + mode_out->key_g = prof.key_g & mask; + mode_out->key_b = prof.key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predicter, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = abs(b - c); + short pb = abs(a - c); + short pc = abs(a + b - c - c); + + if(pc < pa && pc < pb) return (unsigned char)c; + else if(pb < pa) return (unsigned char)b; + else return (unsigned char)a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) + { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) +{ + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) + { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) + { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) + { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) + { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + *w = lodepng_read32bitInt(&in[16]); + *h = lodepng_read32bitInt(&in[20]); + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + if(*w == 0 || *h == 0) + { + CERROR_RETURN_ERROR(state->error, 93); + } + + if(!state->decoder.ignore_crc) + { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) + { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) + { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } + else + { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1); + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1); + } + else + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1); + } + break; + case 4: + if(precon) + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + for(i = bytewidth; i < length; ++i) + { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) + { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; ++y) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) + { + size_t x; + for(x = 0; x < olinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) + { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) + { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned pos = 0, i; + if(color->palette) lodepng_free(color->palette); + color->palettesize = chunkLength / 3; + color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize); + if(!color->palette && color->palettesize) + { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + if(color->palettesize > 256) return 38; /*error: palette too big*/ + + for(i = 0; i != color->palettesize; ++i) + { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned i; + if(color->colortype == LCT_PALETTE) + { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 38; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } + else if(color->colortype == LCT_GREY) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } + else if(color->colortype == LCT_RGB) + { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(info->color.colortype == LCT_PALETTE) + { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } + else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 44; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + /*error: this chunk must be 6 bytes for greyscale image*/ + if(chunkLength != 6) return 45; + + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + char *key = 0, *str = 0; + unsigned i; + + while(!error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = chunkLength < string2_begin ? 0 : chunkLength - string2_begin; + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + str[length] = 0; + for(i = 0; i != length; ++i) str[i] = (char)data[string2_begin + i]; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (const unsigned char*)(&data[string2_begin]), + length, zlibsettings); + if(error) break; + ucvector_push_back(&decoded, 0); + + error = lodepng_add_text(info, key, (char*)decoded.data); + + break; + } + + lodepng_free(key); + ucvector_cleanup(&decoded); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + langtag[length] = 0; + for(i = 0; i != length; ++i) langtag[i] = (char)data[begin + i]; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + transkey[length] = 0; + for(i = 0; i != length; ++i) transkey[i] = (char)data[begin + i]; + + /*read the actual text*/ + begin += length + 1; + + length = chunkLength < begin ? 0 : chunkLength - begin; + + if(compressed) + { + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (const unsigned char*)(&data[begin]), + length, zlibsettings); + if(error) break; + if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/); + + decoded.data[length] = 0; + for(i = 0; i != length; ++i) decoded.data[i] = data[begin + i]; + } + + error = lodepng_add_itext(info, key, langtag, transkey, (char*)decoded.data); + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + ucvector_cleanup(&decoded); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + ucvector scanlines; + size_t predict; + size_t numpixels; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + numpixels = *w * *h; + + /*multiplication overflow*/ + if(*h != 0 && numpixels / *h != *w) CERROR_RETURN(state->error, 92); + /*multiplication overflow possible further below. Allows up to 2^31-1 pixel + bytes with 16-bit RGBA, the rest is room for filter bytes.*/ + if(numpixels > 268435455) CERROR_RETURN(state->error, 92); + + ucvector_init(&idat); + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) + { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) + { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) + { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(state->error, 83 /*alloc fail*/); + for(i = 0; i != chunkLength; ++i) idat.data[oldsize + i] = data[i]; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*IEND chunk*/ + else if(lodepng_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(lodepng_chunk_type_equals(chunk, "PLTE")) + { + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*palette transparency chunk (tRNS)*/ + else if(lodepng_chunk_type_equals(chunk, "tRNS")) + { + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(lodepng_chunk_type_equals(chunk, "bKGD")) + { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } + /*text chunk (tEXt)*/ + else if(lodepng_chunk_type_equals(chunk, "tEXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } + /*compressed text chunk (zTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "zTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "iTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + else if(lodepng_chunk_type_equals(chunk, "tIME")) + { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } + else if(lodepng_chunk_type_equals(chunk, "pHYs")) + { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) + { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) + { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk); + } + + ucvector_init(&scanlines); + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) + { + /*The extra *h is added because this are the filter bytes every scanline starts with*/ + predict = lodepng_get_raw_size_idat(*w, *h, &state->info_png.color) + *h; + } + else + { + /*Adam-7 interlaced: predicted size is the sum of the 7 sub-images sizes*/ + const LodePNGColorMode* color = &state->info_png.color; + predict = 0; + predict += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3); + if(*w > 4) predict += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3); + predict += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, color) + ((*h + 3) >> 3); + if(*w > 2) predict += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, color) + ((*h + 3) >> 2); + predict += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, color) + ((*h + 1) >> 2); + if(*w > 1) predict += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, color) + ((*h + 1) >> 1); + predict += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, color) + ((*h + 0) >> 1); + } + if(!state->error && !ucvector_reserve(&scanlines, predict)) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data, + idat.size, &state->decoder.zlibsettings); + if(!state->error && scanlines.size != predict) state->error = 91; /*decompressed size doesn't match prediction*/ + } + ucvector_cleanup(&idat); + + if(!state->error) + { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) + { + for(i = 0; i < outsize; i++) (*out)[i] = 0; + state->error = postProcessScanlines(*out, scanlines.data, *w, *h, &state->info_png); + } + ucvector_cleanup(&scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) + { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) + { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) + { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) +{ +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) +{ + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) +{ + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data)); + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature, aka the magic bytes*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + lodepng_add32bitInt(&header, w); /*width*/ + lodepng_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitdepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colortype); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlace_method); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i != info->palettesize * 4; ++i) + { + /*add all channels except alpha channel*/ + if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); + } + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colortype == LCT_PALETTE) + { + size_t amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) + { + if(info->palette[4 * (i - 1) + 3] == 255) --amount; + else break; + } + /*add only alpha channel*/ + for(i = 0; i != amount; ++i) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); + } + else if(info->colortype == LCT_GREY) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); + } + } + else if(info->colortype == LCT_RGB) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g & 255)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b >> 8)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b & 255)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&text, 0); /*0 termination char*/ + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + error = zlib_compress(&compressed.data, &compressed.size, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector compressed_data; + ucvector_init(&compressed_data); + error = zlib_compress(&compressed_data.data, &compressed_data.size, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed_data.size; ++i) ucvector_push_back(&data, compressed_data.data[i]); + } + ucvector_cleanup(&compressed_data); + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g & 255)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b >> 8)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b & 255)); + } + else if(info->color.colortype == LCT_PALETTE) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)lodepng_malloc(7); + if(!data) return 83; /*alloc fail*/ + data[0] = (unsigned char)(time->year >> 8); + data[1] = (unsigned char)(time->year & 255); + data[2] = (unsigned char)time->month; + data[3] = (unsigned char)time->day; + data[4] = (unsigned char)time->hour; + data[5] = (unsigned char)time->minute; + data[6] = (unsigned char)time->second; + error = addChunk(out, "tIME", data, 7); + lodepng_free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + lodepng_add32bitInt(&data, info->phys_x); + lodepng_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) + { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } + else + { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) + { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) + { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +/* log2 approximation. A slight bit faster than std::log. */ +static float flog2(float f) +{ + float result = 0; + while(f > 32) { result += 4; f /= 16; } + while(f > 2) { ++result; f /= 2; } + return result + 1.442695f * (f * f * f / 3 - 3 * f * f / 2 + 3 * f - 1.83333f); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(info); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = (w * bpp + 7) / 8; + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (info->colortype == LCT_PALETTE || info->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy == LFS_ZERO) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = 0; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_MINSUM) + { + /*adaptive filtering*/ + size_t sum[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + + if(!error) + { + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + if(type == 0) + { + for(x = 0; x != linebytes; ++x) sum[type] += (unsigned char)(attempt[type][x]); + } + else + { + for(x = 0; x != linebytes; ++x) + { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum[type] += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else if(strategy == LFS_ENTROPY) + { + float sum[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + float smallest = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + for(x = 0; x != 256; ++x) count[x] = 0; + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + sum[type] = 0; + for(x = 0; x != 256; ++x) + { + float p = count[x] / (float)(linebytes + 1); + sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p; + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else if(strategy == LFS_PREDEFINED) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_BRUTE_FORCE) + { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings = settings->zlibsettings; + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) + { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) return 83; /*alloc fail*/ + } + for(y = 0; y != h; ++y) /*try the 5 filter types*/ + { + for(type = 0; type != 5; ++type) + { + unsigned testsize = linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) + { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) + { + size_t x; + for(x = 0; x < ilinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) + { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) + { + addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } + else + { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) + { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) + { + if(bpp < 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +/* +palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... +returns 0 if the palette is opaque, +returns 1 if the palette has a single color with alpha 0 ==> color key +returns 2 if the palette is semi-translucent. +*/ +static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) +{ + size_t i; + unsigned key = 0; + unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/ + for(i = 0; i != palettesize; ++i) + { + if(!key && palette[4 * i + 3] == 0) + { + r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2]; + key = 1; + i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/ + } + else if(palette[4 * i + 3] != 255) return 2; + /*when key, no opaque RGB may have key's RGB*/ + else if(key && r == palette[i * 4 + 0] && g == palette[i * 4 + 1] && b == palette[i * 4 + 2]) return 2; + } + return key; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) +{ + LodePNGInfo info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((state->info_png.color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (state->info_png.color.palettesize == 0 || state->info_png.color.palettesize > 256)) + { + CERROR_RETURN_ERROR(state->error, 68); /*invalid palette size, it is only allowed to be 1-256*/ + } + if(state->encoder.zlibsettings.btype > 2) + { + CERROR_RETURN_ERROR(state->error, 61); /*error: unexisting btype*/ + } + if(state->info_png.interlace_method > 1) + { + CERROR_RETURN_ERROR(state->error, 71); /*error: unexisting interlace mode*/ + } + state->error = checkColorValidity(state->info_png.color.colortype, state->info_png.color.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_init(&info); + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) + { + state->error = lodepng_auto_choose_color(&info.color, image, w, h, &state->info_raw); + } + if (!state->error) + { + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) + { + unsigned char* converted; + size_t size = (w * h * (size_t)lodepng_get_bpp(&info.color) + 7) / 8; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) + { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + } + else + { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + } + } + + /* output all PNG chunks */ + ucvector_init(&outv); + while(!state->error) /*while only executed once, to break on error*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) + { + addChunk_PLTE(&outv, &info.color); + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) + { + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) + { + addChunk_tRNS(&outv, &info.color); + } + if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) + { + addChunk_tRNS(&outv, &info.color); + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) + { + if(strlen(info.text_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.text_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + if(state->encoder.text_compression) + { + addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + } + else + { + addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) + { + if(!strcmp(info.text_keys[i], "LodePNG")) + { + alread_added_id_text = 1; + break; + } + } + if(alread_added_id_text == 0) + { + addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) + { + if(strlen(info.itext_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.itext_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + addChunk_iTXt(&outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + lodepng_info_cleanup(&info); + lodepng_free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) +{ + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) +{ + switch(code) + { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + case 16: return "unexisting code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too big"; /*more than 256 colors*/ + case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette"; + case 40: return "tRNS chunk has wrong size for greyscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for greyscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ + case 62: return "conversion from color to greyscale not supported"; + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "too many pixels, not supported"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) +{ + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) +{ + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) +{ + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) +{ + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() +{ + lodepng_state_init(this); +} + +State::State(const State& other) +{ + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() +{ + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) +{ + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) + { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) +{ + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) +{ + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) + { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) +{ + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + /*will not overflow for any color type if roughly w * h < 268435455*/ + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = w * h; + return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8; +} + +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) +{ + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/source/lodepng.h b/source/lodepng.h new file mode 100644 index 0000000..8e0f742 --- /dev/null +++ b/source/lodepng.h @@ -0,0 +1,1769 @@ +/* +LodePNG version 20180326 + +Copyright (c) 2005-2018 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw).*/ +typedef enum LodePNGColorType +{ + LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/ + LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/ +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings +{ + /* Check LodePNGDecoderSettings for more ignorable errors */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode +{ + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use + lodepng_palette_clear, then for each color use lodepng_palette_add. + If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. When allocated, must be either 0, or have size 1024*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For greyscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/greyscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a greyscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime +{ + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + suggested background color chunk (bKGD) + This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit. + + For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding + the encoder writes the red one. For palette PNGs: When decoding, the RGB value + will be stored, not a palette index. But when encoding, specify the index of + the palette in background_r, the other two are then ignored. + + The decoder does not use this background color to edit the color of pixels. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + unknown chunks + There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ + +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings +{ + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy +{ + /*every filter at zero*/ + LFS_ZERO, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the colors of the image, which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorProfile +{ + unsigned colored; /*not greyscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/ +} LodePNGColorProfile; + +void lodepng_color_profile_init(LodePNGColorProfile* profile); + +/*Get a LodePNGColorProfile of the image.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); +/*The function LodePNG uses internally to decide the PNG color with auto_convert. +Chooses an optimal color model, e.g. grey if only grey pixels, palette if < 256 colors, ...*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings +{ + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState +{ +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +#ifdef LODEPNG_COMPILE_CPP + /* For the lodepng::State subclass. */ + virtual ~LodePNGState(){} +#endif +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the header chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +PNG standard chunk naming conventions: +First byte: uppercase = critical, lowercase = ancillary +Second byte: uppercase = public, lowercase = private +Third byte: must be uppercase +Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState +{ + public: + State(); + State(const State& other); + virtual ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + Some of these are not supported on purpose: LodePNG wants to provide the RGB values + stored in the pixels, not values modified by system dependent gamma or color models. + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to greyscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, greyscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to greyscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +Non supported color conversions: +-color to greyscale: no error is thrown, but the result will look ugly because +only the red channel is taken +-anything to palette when that palette does not have that color in it: in this +case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any grey or grey+alpha, to grey or grey+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards complient. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011 (!): changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2017 Lode Vandevenne +*/ diff --git a/source/main_sdl.c b/source/main_sdl.c new file mode 100644 index 0000000..b63fdc4 --- /dev/null +++ b/source/main_sdl.c @@ -0,0 +1,167 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 + +/* need at least SDL_2.0.0 */ +#define SDL_MIN_X 2 +#define SDL_MIN_Y 0 +#define SDL_MIN_Z 0 +#define SDL_REQUIREDVERSION (SDL_VERSIONNUM(SDL_MIN_X,SDL_MIN_Y,SDL_MIN_Z)) +#define SDL_NEW_VERSION_REJECT (SDL_VERSIONNUM(3,0,0)) + +static void Sys_AtExit (void) +{ + SDL_Quit(); +} + +static void Sys_InitSDL (void) +{ + SDL_version v; + SDL_version *sdl_version = &v; + SDL_GetVersion(&v); + + Sys_Printf("Found SDL version %i.%i.%i\n",sdl_version->major,sdl_version->minor,sdl_version->patch); + if (SDL_VERSIONNUM(sdl_version->major,sdl_version->minor,sdl_version->patch) < SDL_REQUIREDVERSION) + { /*reject running under older SDL versions */ + Sys_Error("You need at least v%d.%d.%d of SDL to run this game.", SDL_MIN_X,SDL_MIN_Y,SDL_MIN_Z); + } + if (SDL_VERSIONNUM(sdl_version->major,sdl_version->minor,sdl_version->patch) >= SDL_NEW_VERSION_REJECT) + { /*reject running under newer (1.3.x) SDL */ + Sys_Error("Your version of SDL library is incompatible with me.\n" + "You need a library version in the line of %d.%d.%d\n", SDL_MIN_X,SDL_MIN_Y,SDL_MIN_Z); + } + + if (SDL_Init(0) < 0) + { + Sys_Error("Couldn't init SDL: %s", SDL_GetError()); + } + atexit(Sys_AtExit); +} + +#define DEFAULT_MEMORY (256 * 1024 * 1024) // ericw -- was 72MB (64-bit) / 64MB (32-bit) + +static quakeparms_t parms; + +// On OS X we call SDL_main from the launcher, but SDL2 doesn't redefine main +// as SDL_main on OS X anymore, so we do it ourselves. +#if defined(__APPLE__) +#define main SDL_main +#endif + +int main(int argc, char *argv[]) +{ + int t; + double time, oldtime, newtime; + + host_parms = &parms; + parms.basedir = "."; + + parms.argc = argc; + parms.argv = argv; + + parms.errstate = 0; + + COM_InitArgv(parms.argc, parms.argv); + + isDedicated = (COM_CheckParm("-dedicated") != 0); + + Sys_InitSDL (); + + Sys_Init(); + + parms.memsize = DEFAULT_MEMORY; + if (COM_CheckParm("-heapsize")) + { + t = COM_CheckParm("-heapsize") + 1; + if (t < com_argc) + parms.memsize = Q_atoi(com_argv[t]) * 1024; + } + + parms.membase = malloc (parms.memsize); + + if (!parms.membase) + Sys_Error ("Not enough memory free; check disk space\n"); + + Sys_Printf("Quake %1.2f (c) id Software\n", VERSION); + Sys_Printf("GLQuake %1.2f (c) id Software\n", GLQUAKE_VERSION); + Sys_Printf("FitzQuake %1.2f (c) John Fitzgibbons\n", FITZQUAKE_VERSION); + Sys_Printf("FitzQuake SDL port (c) SleepwalkR, Baker\n"); + Sys_Printf("QuakeSpasm " QUAKESPASM_VER_STRING " (c) Ozkan Sezer, Eric Wasylishen & others\n"); + + Sys_Printf("Host_Init\n"); + Host_Init(); + + oldtime = Sys_DoubleTime(); + if (isDedicated) + { + while (1) + { + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + + while (time < sys_ticrate.value ) + { + SDL_Delay(1); + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + } + + Host_Frame (time); + oldtime = newtime; + } + } + else + while (1) + { + /* If we have no input focus at all, sleep a bit */ + if (!VID_HasMouseOrInputFocus() || cl.paused) + { + SDL_Delay(16); + } + /* If we're minimised, sleep a bit more */ + if (VID_IsMinimized()) + { + scr_skipupdate = 1; + SDL_Delay(32); + } + else + { + scr_skipupdate = 0; + } + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + + Host_Frame (time); + + if (time < sys_throttle.value && !cls.timedemo) + SDL_Delay(1); + + oldtime = newtime; + } + + return 0; +} + diff --git a/source/mathlib.c b/source/mathlib.c new file mode 100644 index 0000000..c98008e --- /dev/null +++ b/source/mathlib.c @@ -0,0 +1,504 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +vec3_t vec3_origin = {0,0,0}; + +/*-----------------------------------------------------------------*/ + + +//#define DEG2RAD( a ) ( a * M_PI ) / 180.0F +#define DEG2RAD( a ) ( (a) * M_PI_DIV_180 ) //johnfitz + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + +//johnfitz -- removed RotatePointAroundVector() becuase it's no longer used and my compiler fucked it up anyway + +/*-----------------------------------------------------------------*/ + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) +{ + float dist1, dist2; + int sides; + +#if 0 // this is done by the BOX_ON_PLANE_SIDE macro before calling this + // function +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } +#endif + +// general case + switch (p->signbits) + { + case 0: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + Sys_Error ("BoxOnPlaneSide: Bad signbits"); + break; + } + +#if 0 + int i; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; +#endif + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + +#ifdef PARANOID + if (sides == 0) + Sys_Error ("BoxOnPlaneSide: sides==0"); +#endif + + return sides; +} + +//johnfitz -- the opposite of AngleVectors. this takes forward and generates pitch yaw roll +//TODO: take right and up vectors to properly set yaw and roll +void VectorAngles (const vec3_t forward, vec3_t angles) +{ + vec3_t temp; + + temp[0] = forward[0]; + temp[1] = forward[1]; + temp[2] = 0; + angles[PITCH] = -atan2(forward[2], VectorLength(temp)) / M_PI_DIV_180; + angles[YAW] = atan2(forward[1], forward[0]) / M_PI_DIV_180; + angles[ROLL] = 0; +} + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float 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); + + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; +} + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t VectorLength(vec3_t v) +{ + return sqrt(DotProduct(v,v)); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = sqrt(DotProduct(v,v)); + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +/* +=================== +FloorDivMod + +Returns mathematically correct (floor-based) quotient and remainder for +numer and denom, both of which should contain no fractional part. The +quotient must fit in 32 bits. +==================== +*/ + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem) +{ + int q, r; + double x; + +#ifndef PARANOID + if (denom <= 0.0) + Sys_Error ("FloorDivMod: bad denominator %f\n", denom); + +// if ((floor(numer) != numer) || (floor(denom) != denom)) +// Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n", +// numer, denom); +#endif + + if (numer >= 0.0) + { + + x = floor(numer / denom); + q = (int)x; + r = (int)floor(numer - (x * denom)); + } + else + { + // + // perform operations with positive values, and fix mod to make floor-based + // + x = floor(-numer / denom); + q = -(int)x; + r = (int)floor(-numer - (x * denom)); + if (r != 0) + { + q--; + r = (int)denom - r; + } + } + + *quotient = q; + *rem = r; +} + + +/* +=================== +GreatestCommonDivisor +==================== +*/ +int GreatestCommonDivisor (int i1, int i2) +{ + if (i1 > i2) + { + if (i2 == 0) + return (i1); + return GreatestCommonDivisor (i2, i1 % i2); + } + else + { + if (i1 == 0) + return (i2); + return GreatestCommonDivisor (i1, i2 % i1); + } +} + + +/* +=================== +Invert24To16 + +Inverts an 8.24 value to a 16.16 value +==================== +*/ + +fixed16_t Invert24To16(fixed16_t val) +{ + if (val < 256) + return (0xFFFFFFFF); + + return (fixed16_t) + (((double)0x10000 * (double)0x1000000 / (double)val) + 0.5); +} + diff --git a/source/mathlib.h b/source/mathlib.h new file mode 100644 index 0000000..a607905 --- /dev/null +++ b/source/mathlib.h @@ -0,0 +1,122 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __MATHLIB_H +#define __MATHLIB_H + +// mathlib.h + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#define M_PI_DIV_180 (M_PI / 180.0) //johnfitz + +struct mplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255 << 23) /* 7F800000 */ +#if 0 /* macro is violating strict aliasing rules */ +#define IS_NAN(x) (((*(int *) (char *) &x) & nanmask) == nanmask) +#else +static inline int IS_NAN (float x) { + union { float f; int i; } num; + num.f = x; + return ((num.i & nanmask) == nanmask); +} +#endif + +#define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5)) //johnfitz -- from joequake + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define DoublePrecisionDotProduct(x,y) ((double)x[0]*y[0]+(double)x[1]*y[1]+(double)x[2]*y[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];} + +//johnfitz -- courtesy of lordhavoc +// QuakeSpasm: To avoid strict aliasing violations, use a float/int union instead of type punning. +#define VectorNormalizeFast(_v)\ +{\ + union { float f; int i; } _y, _number;\ + _number.f = DotProduct(_v, _v);\ + if (_number.f != 0.0)\ + {\ + _y.i = 0x5f3759df - (_number.i >> 1);\ + _y.f = _y.f * (1.5f - (_number.f * 0.5f * _y.f * _y.f));\ + VectorScale(_v, _y.f, _v);\ + }\ +} + +void TurnVector (vec3_t out, const vec3_t forward, const vec3_t side, float angle); //johnfitz +void VectorAngles (const vec3_t forward, vec3_t angles); //johnfitz + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +float VectorNormalize (vec3_t v); // returns vector length +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem); +fixed16_t Invert24To16(fixed16_t val); +int GreatestCommonDivisor (int i1, int i2); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); +float anglemod(float a); + + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +#endif /* __MATHLIB_H */ + diff --git a/source/menu.c b/source/menu.c new file mode 100644 index 0000000..0a76fc5 --- /dev/null +++ b/source/menu.c @@ -0,0 +1,2782 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "bgmusic.h" + +void (*vid_menucmdfn)(void); //johnfitz +void (*vid_menudrawfn)(void); +void (*vid_menukeyfn)(int key); + +enum m_state_e m_state; + +void M_Menu_Main_f (void); + void M_Menu_SinglePlayer_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_Net_f (void); + void M_Menu_LanConfig_f (void); + void M_Menu_GameOptions_f (void); + void M_Menu_Search_f (void); + void M_Menu_ServerList_f (void); + void M_Menu_Options_f (void); + void M_Menu_Keys_f (void); + void M_Menu_Video_f (void); + void M_Menu_Help_f (void); + void M_Menu_Quit_f (void); + +void M_Main_Draw (void); + void M_SinglePlayer_Draw (void); + void M_Load_Draw (void); + void M_Save_Draw (void); + void M_MultiPlayer_Draw (void); + void M_Setup_Draw (void); + void M_Net_Draw (void); + void M_LanConfig_Draw (void); + void M_GameOptions_Draw (void); + void M_Search_Draw (void); + void M_ServerList_Draw (void); + void M_Options_Draw (void); + void M_Keys_Draw (void); + void M_Video_Draw (void); + void M_Help_Draw (void); + void M_Quit_Draw (void); + +void M_Main_Key (int key); + void M_SinglePlayer_Key (int key); + void M_Load_Key (int key); + void M_Save_Key (int key); + void M_MultiPlayer_Key (int key); + void M_Setup_Key (int key); + void M_Net_Key (int key); + void M_LanConfig_Key (int key); + void M_GameOptions_Key (int key); + void M_Search_Key (int key); + void M_ServerList_Key (int key); + void M_Options_Key (int key); + void M_Keys_Key (int key); + void M_Video_Key (int key); + void M_Help_Key (int key); + void M_Quit_Key (int key); + +qboolean m_entersound; // play after drawing a frame, so caching + // won't disrupt the sound +qboolean m_recursiveDraw; + +enum m_state_e m_return_state; +qboolean m_return_onerror; +char m_return_reason [32]; + +#define StartingGame (m_multiplayer_cursor == 1) +#define JoiningGame (m_multiplayer_cursor == 0) +#define IPXConfig (m_net_cursor == 0) +#define TCPIPConfig (m_net_cursor == 1) + +void M_ConfigureNetSubsystem(void); + +/* +================ +M_DrawCharacter + +Draws one solid graphics character +================ +*/ +void M_DrawCharacter (int cx, int line, int num) +{ + Draw_Character (cx, line, num); +} + +void M_Print (int cx, int cy, const char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, (*str)+128); + str++; + cx += 8; + } +} + +void M_PrintWhite (int cx, int cy, const char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, *str); + str++; + cx += 8; + } +} + +void M_DrawTransPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawTransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom) //johnfitz -- more parameters +{ + Draw_TransPicTranslate (x, y, pic, top, bottom); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawTextBox (int x, int y, int width, int lines) +{ + qpic_t *p; + int cx, cy; + int n; + + // draw left side + cx = x; + cy = y; + p = Draw_CachePic ("gfx/box_tl.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_ml.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_bl.lmp"); + M_DrawTransPic (cx, cy+8, p); + + // draw middle + cx += 8; + while (width > 0) + { + cy = y; + p = Draw_CachePic ("gfx/box_tm.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_mm.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + if (n == 1) + p = Draw_CachePic ("gfx/box_mm2.lmp"); + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_bm.lmp"); + M_DrawTransPic (cx, cy+8, p); + width -= 2; + cx += 16; + } + + // draw right side + cy = y; + p = Draw_CachePic ("gfx/box_tr.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_mr.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_br.lmp"); + M_DrawTransPic (cx, cy+8, p); +} + +//============================================================================= + +int m_save_demonum; + +/* +================ +M_ToggleMenu_f +================ +*/ +void M_ToggleMenu_f (void) +{ + m_entersound = true; + + if (key_dest == key_menu) + { + if (m_state != m_main) + { + M_Menu_Main_f (); + return; + } + + IN_Activate(); + key_dest = key_game; + m_state = m_none; + return; + } + if (key_dest == key_console) + { + Con_ToggleConsole_f (); + } + else + { + M_Menu_Main_f (); + } +} + + +//============================================================================= +/* MAIN MENU */ + +int m_main_cursor; +#define MAIN_ITEMS 5 + + +void M_Menu_Main_f (void) +{ + if (key_dest != key_menu) + { + m_save_demonum = cls.demonum; + cls.demonum = -1; + } + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_main; + m_entersound = true; +} + + +void M_Main_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/ttl_main.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/mainmenu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_main_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_Main_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + IN_Activate(); + key_dest = key_game; + m_state = m_none; + cls.demonum = m_save_demonum; + if (!fitzmode) /* QuakeSpasm customization: */ + break; + if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) + CL_NextDemo (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_main_cursor >= MAIN_ITEMS) + m_main_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_main_cursor < 0) + m_main_cursor = MAIN_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + + 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 */ + +int m_singleplayer_cursor; +#define SINGLEPLAYER_ITEMS 3 + + +void M_Menu_SinglePlayer_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_singleplayer; + m_entersound = true; +} + + +void M_SinglePlayer_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/ttl_sgl.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/sp_menu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_singleplayer_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_SinglePlayer_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) + m_singleplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_singleplayer_cursor < 0) + m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + + switch (m_singleplayer_cursor) + { + case 0: + if (sv.active) + if (!SCR_ModalMessage("Are you sure you want to\nstart a new game?\n", 0.0f)) + break; + IN_Activate(); + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); //johnfitz + Cbuf_AddText ("coop 0\n"); //johnfitz + Cbuf_AddText ("map start\n"); + break; + + case 1: + M_Menu_Load_f (); + break; + + case 2: + M_Menu_Save_f (); + break; + } + } +} + +//============================================================================= +/* LOAD/SAVE MENU */ + +int load_cursor; // 0 < load_cursor < MAX_SAVEGAMES + +#define MAX_SAVEGAMES 20 /* johnfitz -- increased from 12 */ +char m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1]; +int loadable[MAX_SAVEGAMES]; + +void M_ScanSaves (void) +{ + int i, j; + char name[MAX_OSPATH]; + FILE *f; + int version; + + for (i = 0; i < MAX_SAVEGAMES; i++) + { + strcpy (m_filenames[i], "--- UNUSED SLOT ---"); + loadable[i] = false; + q_snprintf (name, sizeof(name), "%s/s%i.sav", com_gamedir, i); + f = fopen (name, "r"); + if (!f) + continue; + fscanf (f, "%i\n", &version); + fscanf (f, "%79s\n", name); + strncpy (m_filenames[i], name, sizeof(m_filenames[i])-1); + + // change _ back to space + for (j = 0; j < SAVEGAME_COMMENT_LENGTH; j++) + { + if (m_filenames[i][j] == '_') + m_filenames[i][j] = ' '; + } + loadable[i] = true; + fclose (f); + } +} + +void M_Menu_Load_f (void) +{ + m_entersound = true; + m_state = m_load; + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + M_ScanSaves (); +} + + +void M_Menu_Save_f (void) +{ + if (!sv.active) + return; + if (cl.intermission) + return; + if (svs.maxclients != 1) + return; + m_entersound = true; + m_state = m_save; + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + M_ScanSaves (); +} + + +void M_Load_Draw (void) +{ + int i; + qpic_t *p; + + p = Draw_CachePic ("gfx/p_load.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + 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)); +} + + +void M_Save_Draw (void) +{ + int i; + qpic_t *p; + + p = Draw_CachePic ("gfx/p_save.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + 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)); +} + + +void M_Load_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_SinglePlayer_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + if (!loadable[load_cursor]) + return; + m_state = m_none; + IN_Activate(); + key_dest = key_game; + + // Host_Loadgame_f can't bring up the loading plaque because too much + // stack space has been used, so do it now + SCR_BeginLoadingPlaque (); + + // issue the load command + Cbuf_AddText (va ("load s%i\n", load_cursor) ); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + + +void M_Save_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_SinglePlayer_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_state = m_none; + IN_Activate(); + key_dest = key_game; + Cbuf_AddText (va("save s%i\n", load_cursor)); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + +//============================================================================= +/* MULTIPLAYER MENU */ + +int m_multiplayer_cursor; +#define MULTIPLAYER_ITEMS 3 + + +void M_Menu_MultiPlayer_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_multiplayer; + m_entersound = true; +} + + +void M_MultiPlayer_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/mp_menu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_multiplayer_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); + + if (ipxAvailable || tcpipAvailable) + return; + M_PrintWhite ((320/2) - ((27*8)/2), 148, "No Communications Available"); +} + + +void M_MultiPlayer_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) + m_multiplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_multiplayer_cursor < 0) + m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (m_multiplayer_cursor) + { + case 0: + if (ipxAvailable || tcpipAvailable) + M_Menu_Net_f (); + break; + + case 1: + if (ipxAvailable || tcpipAvailable) + M_Menu_Net_f (); + break; + + case 2: + M_Menu_Setup_f (); + break; + } + } +} + +//============================================================================= +/* SETUP MENU */ + +int setup_cursor = 4; +int setup_cursor_table[] = {40, 56, 80, 104, 140}; + +char setup_hostname[16]; +char setup_myname[16]; +int setup_oldtop; +int setup_oldbottom; +int setup_top; +int setup_bottom; + +#define NUM_SETUP_CMDS 5 + +void M_Menu_Setup_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_setup; + m_entersound = true; + Q_strcpy(setup_myname, cl_name.string); + Q_strcpy(setup_hostname, hostname.string); + setup_top = setup_oldtop = ((int)cl_color.value) >> 4; + setup_bottom = setup_oldbottom = ((int)cl_color.value) & 15; +} + + +void M_Setup_Draw (void) +{ + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + M_Print (64, 40, "Hostname"); + M_DrawTextBox (160, 32, 16, 1); + M_Print (168, 40, setup_hostname); + + M_Print (64, 56, "Your name"); + M_DrawTextBox (160, 48, 16, 1); + M_Print (168, 56, setup_myname); + + M_Print (64, 80, "Shirt color"); + M_Print (64, 104, "Pants color"); + + M_DrawTextBox (64, 140-8, 14, 1); + M_Print (72, 140, "Accept Changes"); + + p = Draw_CachePic ("gfx/bigbox.lmp"); + M_DrawTransPic (160, 64, p); + p = Draw_CachePic ("gfx/menuplyr.lmp"); + M_DrawTransPicTranslate (172, 72, p, setup_top, setup_bottom); + + M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1)); + + if (setup_cursor == 0) + M_DrawCharacter (168 + 8*strlen(setup_hostname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); + + if (setup_cursor == 1) + M_DrawCharacter (168 + 8*strlen(setup_myname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); +} + + +void M_Setup_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + setup_cursor--; + if (setup_cursor < 0) + setup_cursor = NUM_SETUP_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + setup_cursor++; + if (setup_cursor >= NUM_SETUP_CMDS) + setup_cursor = 0; + break; + + case K_LEFTARROW: + if (setup_cursor < 2) + return; + S_LocalSound ("misc/menu3.wav"); + if (setup_cursor == 2) + setup_top = setup_top - 1; + if (setup_cursor == 3) + setup_bottom = setup_bottom - 1; + break; + case K_RIGHTARROW: + if (setup_cursor < 2) + return; +forward: + S_LocalSound ("misc/menu3.wav"); + if (setup_cursor == 2) + setup_top = setup_top + 1; + if (setup_cursor == 3) + setup_bottom = setup_bottom + 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + if (setup_cursor == 0 || setup_cursor == 1) + return; + + if (setup_cursor == 2 || setup_cursor == 3) + goto forward; + + // setup_cursor == 4 (OK) + if (Q_strcmp(cl_name.string, setup_myname) != 0) + Cbuf_AddText ( va ("name \"%s\"\n", setup_myname) ); + if (Q_strcmp(hostname.string, setup_hostname) != 0) + Cvar_Set("hostname", setup_hostname); + if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom) + Cbuf_AddText( va ("color %i %i\n", setup_top, setup_bottom) ); + m_entersound = true; + M_Menu_MultiPlayer_f (); + break; + + case K_BACKSPACE: + if (setup_cursor == 0) + { + if (strlen(setup_hostname)) + setup_hostname[strlen(setup_hostname)-1] = 0; + } + + if (setup_cursor == 1) + { + if (strlen(setup_myname)) + setup_myname[strlen(setup_myname)-1] = 0; + } + break; + } + + if (setup_top > 13) + setup_top = 0; + if (setup_top < 0) + setup_top = 13; + if (setup_bottom > 13) + setup_bottom = 0; + if (setup_bottom < 0) + setup_bottom = 13; +} + + +void M_Setup_Char (int k) +{ + int l; + + switch (setup_cursor) + { + case 0: + l = strlen(setup_hostname); + if (l < 15) + { + setup_hostname[l+1] = 0; + setup_hostname[l] = k; + } + break; + case 1: + l = strlen(setup_myname); + if (l < 15) + { + setup_myname[l+1] = 0; + setup_myname[l] = k; + } + break; + } +} + + +qboolean M_Setup_TextEntry (void) +{ + return (setup_cursor == 0 || setup_cursor == 1); +} + +//============================================================================= +/* NET MENU */ + +int m_net_cursor; +int m_net_items; + +const char *net_helpMessage [] = +{ +/* .........1.........2.... */ + " Novell network LANs ", + " or Windows 95 DOS-box. ", + " ", + "(LAN=Local Area Network)", + + " Commonly used to play ", + " over the Internet, but ", + " also used on a Local ", + " Area Network. " +}; + +void M_Menu_Net_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_net; + m_entersound = true; + m_net_items = 2; + + if (m_net_cursor >= m_net_items) + m_net_cursor = 0; + m_net_cursor--; + M_Net_Key (K_DOWNARROW); +} + + +void M_Net_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + f = 32; + + if (ipxAvailable) + p = Draw_CachePic ("gfx/netmen3.lmp"); + else + p = Draw_CachePic ("gfx/dim_ipx.lmp"); + M_DrawTransPic (72, f, p); + + f += 19; + if (tcpipAvailable) + p = Draw_CachePic ("gfx/netmen4.lmp"); + else + p = Draw_CachePic ("gfx/dim_tcp.lmp"); + M_DrawTransPic (72, f, p); + + f = (320-26*8)/2; + M_DrawTextBox (f, 96, 24, 4); + f += 8; + M_Print (f, 104, net_helpMessage[m_net_cursor*4+0]); + M_Print (f, 112, net_helpMessage[m_net_cursor*4+1]); + M_Print (f, 120, net_helpMessage[m_net_cursor*4+2]); + M_Print (f, 128, net_helpMessage[m_net_cursor*4+3]); + + f = (int)(realtime * 10)%6; + M_DrawTransPic (54, 32 + m_net_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_Net_Key (int k) +{ +again: + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_MultiPlayer_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_net_cursor >= m_net_items) + m_net_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_net_cursor < 0) + m_net_cursor = m_net_items - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + M_Menu_LanConfig_f (); + break; + } + + if (m_net_cursor == 0 && !ipxAvailable) + goto again; + if (m_net_cursor == 1 && !tcpipAvailable) + goto again; +} + +//============================================================================= +/* OPTIONS MENU */ + +enum +{ + OPT_CUSTOMIZE = 0, + OPT_CONSOLE, // 1 + OPT_DEFAULTS, // 2 + OPT_SCALE, + OPT_SCRSIZE, + OPT_GAMMA, + OPT_CONTRAST, + OPT_MOUSESPEED, + OPT_SBALPHA, + OPT_SNDVOL, + OPT_MUSICVOL, + OPT_MUSICEXT, + OPT_ALWAYRUN, + OPT_INVMOUSE, + OPT_ALWAYSMLOOK, + OPT_LOOKSPRING, + OPT_LOOKSTRAFE, +//#ifdef _WIN32 +// OPT_USEMOUSE, +//#endif + OPT_VIDEO, // This is the last before OPTIONS_ITEMS + OPTIONS_ITEMS +}; + +enum +{ + ALWAYSRUN_OFF = 0, + ALWAYSRUN_VANILLA, + ALWAYSRUN_QUAKESPASM, + ALWAYSRUN_ITEMS +}; + +#define SLIDER_RANGE 10 + +int options_cursor; + +void M_Menu_Options_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_options; + m_entersound = true; +} + + +void M_AdjustSliders (int dir) +{ + int curr_alwaysrun, target_alwaysrun; + float f, l; + + S_LocalSound ("misc/menu3.wav"); + + switch (options_cursor) + { + case OPT_SCALE: // console and menu scale + l = ((vid.width + 31) / 32) / 10.0; + f = scr_conscale.value + dir * .1; + if (f < 1) f = 1; + else if(f > l) f = l; + Cvar_SetValue ("scr_conscale", f); + Cvar_SetValue ("scr_menuscale", f); + Cvar_SetValue ("scr_sbarscale", f); + break; + case OPT_SCRSIZE: // screen size + f = scr_viewsize.value + dir * 10; + if (f > 120) f = 120; + else if(f < 30) f = 30; + Cvar_SetValue ("viewsize", f); + break; + case OPT_GAMMA: // gamma + f = vid_gamma.value - dir * 0.05; + if (f < 0.5) f = 0.5; + else if (f > 1) f = 1; + Cvar_SetValue ("gamma", f); + break; + case OPT_CONTRAST: // contrast + f = vid_contrast.value + dir * 0.1; + if (f < 1) f = 1; + else if (f > 2) f = 2; + Cvar_SetValue ("contrast", f); + break; + case OPT_MOUSESPEED: // mouse speed + f = sensitivity.value + dir * 0.5; + if (f > 11) f = 11; + else if (f < 1) f = 1; + Cvar_SetValue ("sensitivity", f); + break; + case OPT_SBALPHA: // statusbar alpha + f = scr_sbaralpha.value - dir * 0.05; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("scr_sbaralpha", f); + break; + case OPT_MUSICVOL: // music volume + f = bgmvolume.value + dir * 0.1; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("bgmvolume", f); + break; + case OPT_MUSICEXT: // enable external music vs cdaudio + Cvar_Set ("bgm_extmusic", bgm_extmusic.value ? "0" : "1"); + break; + case OPT_SNDVOL: // sfx volume + f = sfxvolume.value + dir * 0.1; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("volume", f); + break; + + case OPT_ALWAYRUN: // always run + if (cl_alwaysrun.value) + curr_alwaysrun = ALWAYSRUN_QUAKESPASM; + else if (cl_forwardspeed.value > 200) + curr_alwaysrun = ALWAYSRUN_VANILLA; + else + curr_alwaysrun = ALWAYSRUN_OFF; + + target_alwaysrun = (ALWAYSRUN_ITEMS + curr_alwaysrun + dir) % ALWAYSRUN_ITEMS; + + if (target_alwaysrun == ALWAYSRUN_VANILLA) + { + Cvar_SetValue ("cl_alwaysrun", 0); + Cvar_SetValue ("cl_forwardspeed", 400); + Cvar_SetValue ("cl_backspeed", 400); + } + else if (target_alwaysrun == ALWAYSRUN_QUAKESPASM) + { + Cvar_SetValue ("cl_alwaysrun", 1); + Cvar_SetValue ("cl_forwardspeed", 200); + Cvar_SetValue ("cl_backspeed", 200); + } + else // ALWAYSRUN_OFF + { + Cvar_SetValue ("cl_alwaysrun", 0); + Cvar_SetValue ("cl_forwardspeed", 200); + Cvar_SetValue ("cl_backspeed", 200); + } + break; + + case OPT_INVMOUSE: // invert mouse + Cvar_SetValue ("m_pitch", -m_pitch.value); + break; + + case OPT_ALWAYSMLOOK: + if (in_mlook.state & 1) + Cbuf_AddText("-mlook"); + else + Cbuf_AddText("+mlook"); + break; + + case OPT_LOOKSPRING: // lookspring + Cvar_Set ("lookspring", lookspring.value ? "0" : "1"); + break; + + case OPT_LOOKSTRAFE: // lookstrafe + Cvar_Set ("lookstrafe", lookstrafe.value ? "0" : "1"); + break; + } +} + + +void M_DrawSlider (int x, int y, float range) +{ + int i; + + if (range < 0) + range = 0; + if (range > 1) + range = 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); +} + +void M_DrawCheckbox (int x, int y, int on) +{ +#if 0 + if (on) + M_DrawCharacter (x, y, 131); + else + M_DrawCharacter (x, y, 129); +#endif + if (on) + M_Print (x, y, "on"); + else + M_Print (x, y, "off"); +} + +void M_Options_Draw (void) +{ + float r, l; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_option.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + // Draw the items in the order of the enum defined above: + // OPT_CUSTOMIZE: + M_Print (16, 32, " Controls"); + // OPT_CONSOLE: + M_Print (16, 32 + 8*OPT_CONSOLE, " Goto console"); + // OPT_DEFAULTS: + M_Print (16, 32 + 8*OPT_DEFAULTS, " Reset config"); + + // OPT_SCALE: + M_Print (16, 32 + 8*OPT_SCALE, " Scale"); + l = (vid.width / 320.0) - 1; + r = l > 0 ? (scr_conscale.value - 1) / l : 0; + M_DrawSlider (220, 32 + 8*OPT_SCALE, r); + + // OPT_SCRSIZE: + M_Print (16, 32 + 8*OPT_SCRSIZE, " Screen size"); + r = (scr_viewsize.value - 30) / (120 - 30); + M_DrawSlider (220, 32 + 8*OPT_SCRSIZE, r); + + // OPT_GAMMA: + M_Print (16, 32 + 8*OPT_GAMMA, " Brightness"); + r = (1.0 - vid_gamma.value) / 0.5; + M_DrawSlider (220, 32 + 8*OPT_GAMMA, r); + + // OPT_CONTRAST: + M_Print (16, 32 + 8*OPT_CONTRAST, " Contrast"); + r = vid_contrast.value - 1.0; + M_DrawSlider (220, 32 + 8*OPT_CONTRAST, r); + + // OPT_MOUSESPEED: + M_Print (16, 32 + 8*OPT_MOUSESPEED, " Mouse Speed"); + r = (sensitivity.value - 1)/10; + M_DrawSlider (220, 32 + 8*OPT_MOUSESPEED, r); + + // OPT_SBALPHA: + M_Print (16, 32 + 8*OPT_SBALPHA, " Statusbar alpha"); + r = (1.0 - scr_sbaralpha.value) ; // scr_sbaralpha range is 1.0 to 0.0 + M_DrawSlider (220, 32 + 8*OPT_SBALPHA, r); + + // OPT_SNDVOL: + M_Print (16, 32 + 8*OPT_SNDVOL, " Sound Volume"); + r = sfxvolume.value; + M_DrawSlider (220, 32 + 8*OPT_SNDVOL, r); + + // OPT_MUSICVOL: + M_Print (16, 32 + 8*OPT_MUSICVOL, " Music Volume"); + r = bgmvolume.value; + M_DrawSlider (220, 32 + 8*OPT_MUSICVOL, r); + + // OPT_MUSICEXT: + M_Print (16, 32 + 8*OPT_MUSICEXT, " External Music"); + M_DrawCheckbox (220, 32 + 8*OPT_MUSICEXT, bgm_extmusic.value); + + // OPT_ALWAYRUN: + M_Print (16, 32 + 8*OPT_ALWAYRUN, " Always Run"); + if (cl_alwaysrun.value) + M_Print (220, 32 + 8*OPT_ALWAYRUN, "quakespasm"); + else if (cl_forwardspeed.value > 200.0) + M_Print (220, 32 + 8*OPT_ALWAYRUN, "vanilla"); + else + M_Print (220, 32 + 8*OPT_ALWAYRUN, "off"); + + // OPT_INVMOUSE: + M_Print (16, 32 + 8*OPT_INVMOUSE, " Invert Mouse"); + M_DrawCheckbox (220, 32 + 8*OPT_INVMOUSE, m_pitch.value < 0); + + // OPT_ALWAYSMLOOK: + M_Print (16, 32 + 8*OPT_ALWAYSMLOOK, " Mouse Look"); + M_DrawCheckbox (220, 32 + 8*OPT_ALWAYSMLOOK, in_mlook.state & 1); + + // OPT_LOOKSPRING: + M_Print (16, 32 + 8*OPT_LOOKSPRING, " Lookspring"); + M_DrawCheckbox (220, 32 + 8*OPT_LOOKSPRING, lookspring.value); + + // OPT_LOOKSTRAFE: + M_Print (16, 32 + 8*OPT_LOOKSTRAFE, " Lookstrafe"); + M_DrawCheckbox (220, 32 + 8*OPT_LOOKSTRAFE, lookstrafe.value); + + // OPT_VIDEO: + if (vid_menudrawfn) + M_Print (16, 32 + 8*OPT_VIDEO, " Video Options"); + +// cursor + M_DrawCharacter (200, 32 + options_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Options_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (options_cursor) + { + case OPT_CUSTOMIZE: + M_Menu_Keys_f (); + break; + case OPT_CONSOLE: + m_state = m_none; + Con_ToggleConsole_f (); + break; + case OPT_DEFAULTS: + if (SCR_ModalMessage("This will reset all controls\n" + "and stored cvars. Continue? (y/n)\n", 15.0f)) + { + Cbuf_AddText ("resetcfg\n"); + Cbuf_AddText ("exec default.cfg\n"); + } + break; + case OPT_VIDEO: + M_Menu_Video_f (); + break; + default: + M_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + options_cursor--; + if (options_cursor < 0) + options_cursor = OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + options_cursor++; + if (options_cursor >= OPTIONS_ITEMS) + options_cursor = 0; + break; + + case K_LEFTARROW: + M_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_AdjustSliders (1); + break; + } + + if (options_cursor == OPTIONS_ITEMS - 1 && vid_menudrawfn == NULL) + { + if (k == K_UPARROW) + options_cursor = OPTIONS_ITEMS - 2; + else + options_cursor = 0; + } +} + +//============================================================================= +/* KEYS MENU */ + +const char *bindnames[][2] = +{ + {"+attack", "attack"}, + {"impulse 10", "next weapon"}, + {"impulse 12", "prev 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"} +}; + +#define NUMCOMMANDS (sizeof(bindnames)/sizeof(bindnames[0])) + +static int keys_cursor; +static qboolean bind_grab; + +void M_Menu_Keys_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_keys; + m_entersound = true; +} + + +void M_FindKeysForCommand (const char *command, int *threekeys) +{ + int count; + int j; + int l; + char *b; + + threekeys[0] = threekeys[1] = threekeys[2] = -1; + l = strlen(command); + count = 0; + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + { + threekeys[count] = j; + count++; + if (count == 3) + break; + } + } +} + +void M_UnbindCommand (const char *command) +{ + int j; + int l; + char *b; + + l = strlen(command); + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + Key_SetBinding (j, NULL); + } +} + +extern qpic_t *pic_up, *pic_down; + +void M_Keys_Draw (void) +{ + int i, x, y; + int keys[3]; + const char *name; + qpic_t *p; + + p = Draw_CachePic ("gfx/ttl_cstm.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + 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 < (int)NUMCOMMANDS; i++) + { + y = 48 + 8*i; + + M_Print (16, y, bindnames[i][1]); + + M_FindKeysForCommand (bindnames[i][0], keys); + + if (keys[0] == -1) + { + M_Print (140, y, "???"); + } + else + { + name = Key_KeynumToString (keys[0]); + M_Print (140, y, name); + x = strlen(name) * 8; + if (keys[1] != -1) + { + name = Key_KeynumToString (keys[1]); + M_Print (140 + x + 8, y, "or"); + M_Print (140 + x + 32, y, name); + x = x + 32 + strlen(name) * 8; + if (keys[2] != -1) + { + M_Print (140 + x + 8, y, "or"); + M_Print (140 + x + 32, y, Key_KeynumToString (keys[2])); + } + } + } + } + + if (bind_grab) + M_DrawCharacter (130, 48 + keys_cursor*8, '='); + else + M_DrawCharacter (130, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Keys_Key (int k) +{ + char cmd[80]; + int keys[3]; + + if (bind_grab) + { // defining a key + S_LocalSound ("misc/menu1.wav"); + if ((k != K_ESCAPE) && (k != '`')) + { + sprintf (cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor][0]); + Cbuf_InsertText (cmd); + } + + bind_grab = false; + IN_Deactivate(modestate == MS_WINDOWED); // deactivate because we're returning to the menu + return; + } + + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Options_f (); + break; + + case K_LEFTARROW: + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + keys_cursor--; + if (keys_cursor < 0) + keys_cursor = NUMCOMMANDS-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + keys_cursor++; + if (keys_cursor >= (int)NUMCOMMANDS) + keys_cursor = 0; + break; + + case K_ENTER: // go into bind mode + case K_KP_ENTER: + case K_ABUTTON: + M_FindKeysForCommand (bindnames[keys_cursor][0], keys); + S_LocalSound ("misc/menu2.wav"); + if (keys[2] != -1) + M_UnbindCommand (bindnames[keys_cursor][0]); + bind_grab = true; + IN_Activate(); // activate to allow mouse key binding + break; + + case K_BACKSPACE: // delete bindings + case K_DEL: + S_LocalSound ("misc/menu2.wav"); + M_UnbindCommand (bindnames[keys_cursor][0]); + break; + } +} + +//============================================================================= +/* VIDEO MENU */ + +void M_Menu_Video_f (void) +{ + (*vid_menucmdfn) (); //johnfitz +} + + +void M_Video_Draw (void) +{ + (*vid_menudrawfn) (); +} + + +void M_Video_Key (int key) +{ + (*vid_menukeyfn) (key); +} + +//============================================================================= +/* HELP MENU */ + +int help_page; +#define NUM_HELP_PAGES 6 + + +void M_Menu_Help_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_help; + m_entersound = true; + help_page = 0; +} + + + +void M_Help_Draw (void) +{ + M_DrawPic (0, 0, Draw_CachePic ( va("gfx/help%i.lmp", help_page)) ); +} + + +void M_Help_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + 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; + } + +} + +//============================================================================= +/* QUIT MENU */ + +int msgNumber; +enum m_state_e m_quit_prevstate; +qboolean wasInMenus; + +void M_Menu_Quit_f (void) +{ + if (m_state == m_quit) + return; + wasInMenus = (key_dest == key_menu); + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_quit_prevstate = m_state; + m_state = m_quit; + m_entersound = true; + msgNumber = rand()&7; +} + + +void M_Quit_Key (int key) +{ + if (key == K_ESCAPE) + { + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_entersound = true; + } + else + { + IN_Activate(); + key_dest = key_game; + m_state = m_none; + } + } +} + + +void M_Quit_Char (int key) +{ + switch (key) + { + case 'n': + case 'N': + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_entersound = true; + } + else + { + IN_Activate(); + key_dest = key_game; + m_state = m_none; + } + break; + + case 'y': + case 'Y': + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + Host_Quit_f (); + break; + + default: + break; + } + +} + + +qboolean M_Quit_TextEntry (void) +{ + return true; +} + + +void M_Quit_Draw (void) //johnfitz -- modified for new quit message +{ + char msg1[40]; + char msg2[] = "by Ozkan, Ericw & Stevenaaus"; /* msg2/msg3 are mostly [40] */ + char msg3[] = "Press y to quit"; + int boxlen; + + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_recursiveDraw = true; + M_Draw (); + m_state = m_quit; + } + + sprintf(msg1, "QuakeSpasm " QUAKESPASM_VER_STRING); + + //okay, this is kind of fucked up. M_DrawTextBox will always act as if + //width is even. Also, the width and lines values are for the interior of the box, + //but the x and y values include the border. + boxlen = q_max(strlen(msg1), q_max((sizeof(msg2)-1),(sizeof(msg3)-1))) + 1; + if (boxlen & 1) boxlen++; + M_DrawTextBox (160-4*(boxlen+2), 76, boxlen, 4); + + //now do the text + M_Print (160-4*strlen(msg1), 88, msg1); + M_Print (160-4*(sizeof(msg2)-1), 96, msg2); + M_PrintWhite (160-4*(sizeof(msg3)-1), 104, msg3); +} + +//============================================================================= +/* LAN CONFIG MENU */ + +int lanConfig_cursor = -1; +int lanConfig_cursor_table [] = {72, 92, 124}; +#define NUM_LANCONFIG_CMDS 3 + +int lanConfig_port; +char lanConfig_portname[6]; +char lanConfig_joinname[22]; + +void M_Menu_LanConfig_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_lanconfig; + m_entersound = true; + if (lanConfig_cursor == -1) + { + if (JoiningGame && TCPIPConfig) + lanConfig_cursor = 2; + else + lanConfig_cursor = 1; + } + if (StartingGame && lanConfig_cursor == 2) + lanConfig_cursor = 1; + lanConfig_port = DEFAULTnet_hostport; + sprintf(lanConfig_portname, "%u", lanConfig_port); + + m_return_onerror = false; + m_return_reason[0] = 0; +} + + +void M_LanConfig_Draw (void) +{ + qpic_t *p; + int basex; + const char *startJoin; + const char *protocol; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + basex = (320-p->width)/2; + M_DrawPic (basex, 4, p); + + if (StartingGame) + startJoin = "New Game"; + else + startJoin = "Join Game"; + if (IPXConfig) + protocol = "IPX"; + else + protocol = "TCP/IP"; + M_Print (basex, 32, va ("%s - %s", startJoin, protocol)); + basex += 8; + + M_Print (basex, 52, "Address:"); + if (IPXConfig) + M_Print (basex+9*8, 52, my_ipx_address); + else + M_Print (basex+9*8, 52, my_tcpip_address); + + M_Print (basex, lanConfig_cursor_table[0], "Port"); + M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, 6, 1); + M_Print (basex+9*8, lanConfig_cursor_table[0], lanConfig_portname); + + if (JoiningGame) + { + M_Print (basex, lanConfig_cursor_table[1], "Search for local games..."); + M_Print (basex, 108, "Join game at:"); + M_DrawTextBox (basex+8, lanConfig_cursor_table[2]-8, 22, 1); + M_Print (basex+16, lanConfig_cursor_table[2], 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 [0], 10+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 2) + M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [2], 10+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_PrintWhite (basex, 148, m_return_reason); +} + + +void M_LanConfig_Key (int key) +{ + int l; + + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Net_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + lanConfig_cursor--; + if (lanConfig_cursor < 0) + lanConfig_cursor = NUM_LANCONFIG_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + lanConfig_cursor++; + if (lanConfig_cursor >= NUM_LANCONFIG_CMDS) + lanConfig_cursor = 0; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + if (lanConfig_cursor == 0) + break; + + m_entersound = true; + + M_ConfigureNetSubsystem (); + + if (lanConfig_cursor == 1) + { + if (StartingGame) + { + M_Menu_GameOptions_f (); + break; + } + M_Menu_Search_f(); + break; + } + + if (lanConfig_cursor == 2) + { + m_return_state = m_state; + m_return_onerror = true; + IN_Activate(); + key_dest = key_game; + m_state = m_none; + Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); + break; + } + + break; + + case K_BACKSPACE: + if (lanConfig_cursor == 0) + { + if (strlen(lanConfig_portname)) + lanConfig_portname[strlen(lanConfig_portname)-1] = 0; + } + + if (lanConfig_cursor == 2) + { + if (strlen(lanConfig_joinname)) + lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; + } + break; + } + + if (StartingGame && lanConfig_cursor == 2) + { + if (key == K_UPARROW) + lanConfig_cursor = 1; + else + lanConfig_cursor = 0; + } + + l = Q_atoi(lanConfig_portname); + if (l > 65535) + l = lanConfig_port; + else + lanConfig_port = l; + sprintf(lanConfig_portname, "%u", lanConfig_port); +} + + +void M_LanConfig_Char (int key) +{ + int l; + + switch (lanConfig_cursor) + { + case 0: + if (key < '0' || key > '9') + return; + l = strlen(lanConfig_portname); + if (l < 5) + { + lanConfig_portname[l+1] = 0; + lanConfig_portname[l] = key; + } + break; + case 2: + l = strlen(lanConfig_joinname); + if (l < 21) + { + lanConfig_joinname[l+1] = 0; + lanConfig_joinname[l] = key; + } + break; + } +} + + +qboolean M_LanConfig_TextEntry (void) +{ + return (lanConfig_cursor == 0 || lanConfig_cursor == 2); +} + +//============================================================================= +/* GAME OPTIONS MENU */ + +typedef struct +{ + const char *name; + const char *description; +} level_t; + +level_t levels[] = +{ + {"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"} +}; + +//MED 01/06/97 added hipnotic levels +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 +}; + +//PGM 01/07/97 added rogue levels +//PGM 03/02/97 added dmatch level +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"} +}; + +typedef struct +{ + const char *description; + int firstLevel; + int levels; +} episode_t; + +episode_t episodes[] = +{ + {"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 episodes +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 episodes +//PGM 03/02/97 added dmatch episode +episode_t rogueepisodes[] = +{ + {"Introduction", 0, 1}, + {"Hell's Fortress", 1, 7}, + {"Corridors of Time", 8, 8}, + {"Deathmatch Arena", 16, 1} +}; + +int startepisode; +int startlevel; +int maxplayers; +qboolean m_serverInfoMessage = false; +double m_serverInfoMessageTime; + +void M_Menu_GameOptions_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_gameoptions; + m_entersound = true; + if (maxplayers == 0) + maxplayers = svs.maxclients; + if (maxplayers < 2) + maxplayers = svs.maxclientslimit; +} + + +int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 112, 120}; +#define NUM_GAMEOPTIONS 9 +int gameoptions_cursor; + +void M_GameOptions_Draw (void) +{ + qpic_t *p; + int x; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + 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) ); + + M_Print (0, 64, " Game Type"); + if (coop.value) + M_Print (160, 64, "Cooperative"); + else + M_Print (160, 64, "Deathmatch"); + + M_Print (0, 72, " Teamplay"); + if (rogue) + { + const char *msg; + + switch((int)teamplay.value) + { + 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((int)teamplay.value) + { + case 1: msg = "No Friendly Fire"; break; + case 2: msg = "Friendly Fire"; break; + default: msg = "Off"; break; + } + M_Print (160, 72, msg); + } + + M_Print (0, 80, " Skill"); + if (skill.value == 0) + M_Print (160, 80, "Easy difficulty"); + else if (skill.value == 1) + M_Print (160, 80, "Normal difficulty"); + else if (skill.value == 2) + M_Print (160, 80, "Hard difficulty"); + else + M_Print (160, 80, "Nightmare difficulty"); + + M_Print (0, 88, " Frag Limit"); + if (fraglimit.value == 0) + M_Print (160, 88, "none"); + else + M_Print (160, 88, va("%i frags", (int)fraglimit.value)); + + M_Print (0, 96, " Time Limit"); + if (timelimit.value == 0) + M_Print (160, 96, "none"); + else + M_Print (160, 96, va("%i minutes", (int)timelimit.value)); + + M_Print (0, 112, " Episode"); + // MED 01/06/97 added hipnotic episodes + if (hipnotic) + M_Print (160, 112, hipnoticepisodes[startepisode].description); + // PGM 01/07/97 added rogue episodes + else if (rogue) + M_Print (160, 112, rogueepisodes[startepisode].description); + else + M_Print (160, 112, episodes[startepisode].description); + + M_Print (0, 120, " Level"); + // MED 01/06/97 added hipnotic episodes + if (hipnotic) + { + M_Print (160, 120, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name); + } + // PGM 01/07/97 added rogue episodes + else if (rogue) + { + M_Print (160, 120, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name); + } + else + { + M_Print (160, 120, levels[episodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, levels[episodes[startepisode].firstLevel + startlevel].name); + } + +// line cursor + 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 4 players "); + M_Print (x, 154, " requires using command "); + M_Print (x, 162, "line parameters; please "); + M_Print (x, 170, " see techinfo.txt. "); + } + else + { + m_serverInfoMessage = false; + } + } +} + + +void M_NetStart_Change (int dir) +{ + int count; + float f; + + switch (gameoptions_cursor) + { + case 1: + maxplayers += dir; + if (maxplayers > svs.maxclientslimit) + { + maxplayers = svs.maxclientslimit; + m_serverInfoMessage = true; + m_serverInfoMessageTime = realtime; + } + if (maxplayers < 2) + maxplayers = 2; + break; + + case 2: + Cvar_Set ("coop", coop.value ? "0" : "1"); + break; + + case 3: + count = (rogue) ? 6 : 2; + f = teamplay.value + dir; + if (f > count) f = 0; + else if (f < 0) f = count; + Cvar_SetValue ("teamplay", f); + break; + + case 4: + f = skill.value + dir; + if (f > 3) f = 0; + else if (f < 0) f = 3; + Cvar_SetValue ("skill", f); + break; + + case 5: + f = fraglimit.value + dir * 10; + if (f > 100) f = 0; + else if (f < 0) f = 100; + Cvar_SetValue ("fraglimit", f); + break; + + case 6: + f = timelimit.value + dir * 5; + if (f > 60) f = 0; + else if (f < 0) f = 60; + Cvar_SetValue ("timelimit", f); + break; + + case 7: + startepisode += dir; + //MED 01/06/97 added hipnotic count + if (hipnotic) + count = 6; + //PGM 01/07/97 added rogue count + //PGM 03/02/97 added 1 for dmatch episode + else if (rogue) + count = 4; + else if (registered.value) + count = 7; + else + count = 2; + + if (startepisode < 0) + startepisode = count - 1; + + if (startepisode >= count) + startepisode = 0; + + startlevel = 0; + break; + + case 8: + startlevel += dir; + //MED 01/06/97 added hipnotic episodes + if (hipnotic) + count = hipnoticepisodes[startepisode].levels; + //PGM 01/06/97 added hipnotic episodes + else if (rogue) + count = rogueepisodes[startepisode].levels; + else + count = episodes[startepisode].levels; + + if (startlevel < 0) + startlevel = count - 1; + + if (startlevel >= count) + startlevel = 0; + break; + } +} + +void M_GameOptions_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Net_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + gameoptions_cursor--; + if (gameoptions_cursor < 0) + gameoptions_cursor = NUM_GAMEOPTIONS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + gameoptions_cursor++; + if (gameoptions_cursor >= NUM_GAMEOPTIONS) + gameoptions_cursor = 0; + break; + + case K_LEFTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("misc/menu3.wav"); + M_NetStart_Change (-1); + break; + + case K_RIGHTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("misc/menu3.wav"); + M_NetStart_Change (1); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + if (gameoptions_cursor == 0) + { + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("listen 0\n"); // so host_netport will be re-examined + Cbuf_AddText ( va ("maxplayers %u\n", maxplayers) ); + SCR_BeginLoadingPlaque (); + + if (hipnotic) + Cbuf_AddText ( va ("map %s\n", hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name) ); + else if (rogue) + Cbuf_AddText ( va ("map %s\n", roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name) ); + else + Cbuf_AddText ( va ("map %s\n", levels[episodes[startepisode].firstLevel + startlevel].name) ); + + return; + } + + M_NetStart_Change (1); + break; + } +} + +//============================================================================= +/* SEARCH MENU */ + +qboolean searchComplete = false; +double searchCompleteTime; + +void M_Menu_Search_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_search; + m_entersound = false; + slistSilent = true; + slistLocal = false; + searchComplete = false; + NET_Slist_f(); + +} + + +void M_Search_Draw (void) +{ + qpic_t *p; + int x; + + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + x = (320/2) - ((12*8)/2) + 4; + M_DrawTextBox (x-8, 32, 12, 1); + M_Print (x, 40, "Searching..."); + + if(slistInProgress) + { + NET_Poll(); + return; + } + + if (! searchComplete) + { + searchComplete = true; + searchCompleteTime = realtime; + } + + if (hostCacheCount) + { + M_Menu_ServerList_f (); + return; + } + + M_PrintWhite ((320/2) - ((22*8)/2), 64, "No Quake servers found"); + if ((realtime - searchCompleteTime) < 3.0) + return; + + M_Menu_LanConfig_f (); +} + + +void M_Search_Key (int key) +{ +} + +//============================================================================= +/* SLIST MENU */ + +int slist_cursor; +qboolean slist_sorted; + +void M_Menu_ServerList_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_slist; + m_entersound = true; + slist_cursor = 0; + m_return_onerror = false; + m_return_reason[0] = 0; + slist_sorted = false; +} + + +void M_ServerList_Draw (void) +{ + int n; + qpic_t *p; + + if (!slist_sorted) + { + slist_sorted = true; + NET_SlistSort (); + } + + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + for (n = 0; n < hostCacheCount; n++) + M_Print (16, 32 + 8*n, NET_SlistPrintServer (n)); + M_DrawCharacter (0, 32 + slist_cursor*8, 12+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_PrintWhite (16, 148, m_return_reason); +} + + +void M_ServerList_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_LanConfig_f (); + break; + + case K_SPACE: + M_Menu_Search_f (); + break; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + slist_cursor--; + if (slist_cursor < 0) + slist_cursor = hostCacheCount - 1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + slist_cursor++; + if (slist_cursor >= hostCacheCount) + slist_cursor = 0; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + m_return_state = m_state; + m_return_onerror = true; + slist_sorted = false; + IN_Activate(); + key_dest = key_game; + m_state = m_none; + Cbuf_AddText ( va ("connect \"%s\"\n", NET_SlistPrintServerName(slist_cursor)) ); + break; + + default: + break; + } + +} + +//============================================================================= +/* Menu Subsystem */ + + +void M_Init (void) +{ + Cmd_AddCommand ("togglemenu", M_ToggleMenu_f); + + Cmd_AddCommand ("menu_main", M_Menu_Main_f); + Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f); + Cmd_AddCommand ("menu_load", M_Menu_Load_f); + Cmd_AddCommand ("menu_save", M_Menu_Save_f); + Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f); + Cmd_AddCommand ("menu_setup", M_Menu_Setup_f); + Cmd_AddCommand ("menu_options", M_Menu_Options_f); + Cmd_AddCommand ("menu_keys", M_Menu_Keys_f); + Cmd_AddCommand ("menu_video", M_Menu_Video_f); + Cmd_AddCommand ("help", M_Menu_Help_f); + Cmd_AddCommand ("menu_quit", M_Menu_Quit_f); +} + + +void M_Draw (void) +{ + if (m_state == m_none || key_dest != key_menu) + return; + + if (!m_recursiveDraw) + { + if (scr_con_current) + { + Draw_ConsoleBackground (); + S_ExtraUpdate (); + } + + Draw_FadeScreen (); //johnfitz -- fade even if console fills screen + } + else + { + m_recursiveDraw = false; + } + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + switch (m_state) + { + case m_none: + break; + + case m_main: + M_Main_Draw (); + break; + + case m_singleplayer: + M_SinglePlayer_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_net: + M_Net_Draw (); + break; + + case m_options: + M_Options_Draw (); + break; + + case m_keys: + M_Keys_Draw (); + break; + + case m_video: + M_Video_Draw (); + break; + + case m_help: + M_Help_Draw (); + break; + + case m_quit: + if (!fitzmode) + { /* QuakeSpasm customization: */ + /* Quit now! S.A. */ + key_dest = key_console; + Host_Quit_f (); + } + M_Quit_Draw (); + break; + + case m_lanconfig: + M_LanConfig_Draw (); + break; + + case m_gameoptions: + M_GameOptions_Draw (); + break; + + case m_search: + M_Search_Draw (); + break; + + case m_slist: + M_ServerList_Draw (); + break; + } + + if (m_entersound) + { + S_LocalSound ("misc/menu2.wav"); + m_entersound = false; + } + + S_ExtraUpdate (); +} + + +void M_Keydown (int key) +{ + switch (m_state) + { + case m_none: + return; + + case m_main: + M_Main_Key (key); + return; + + case m_singleplayer: + M_SinglePlayer_Key (key); + return; + + case m_load: + M_Load_Key (key); + return; + + case m_save: + M_Save_Key (key); + return; + + case m_multiplayer: + M_MultiPlayer_Key (key); + return; + + case m_setup: + M_Setup_Key (key); + return; + + case m_net: + M_Net_Key (key); + return; + + case m_options: + M_Options_Key (key); + return; + + case m_keys: + M_Keys_Key (key); + return; + + case m_video: + M_Video_Key (key); + return; + + case m_help: + M_Help_Key (key); + return; + + case m_quit: + M_Quit_Key (key); + return; + + case m_lanconfig: + M_LanConfig_Key (key); + return; + + case m_gameoptions: + M_GameOptions_Key (key); + return; + + case m_search: + M_Search_Key (key); + break; + + case m_slist: + M_ServerList_Key (key); + return; + } +} + + +void M_Charinput (int key) +{ + switch (m_state) + { + case m_setup: + M_Setup_Char (key); + return; + case m_quit: + M_Quit_Char (key); + return; + case m_lanconfig: + M_LanConfig_Char (key); + return; + default: + return; + } +} + + +qboolean M_TextEntry (void) +{ + switch (m_state) + { + case m_setup: + return M_Setup_TextEntry (); + case m_quit: + return M_Quit_TextEntry (); + case m_lanconfig: + return M_LanConfig_TextEntry (); + default: + return false; + } +} + + +void M_ConfigureNetSubsystem(void) +{ +// enable/disable net systems to match desired config + Cbuf_AddText ("stopdemo\n"); + + if (IPXConfig || TCPIPConfig) + net_hostport = lanConfig_port; +} + diff --git a/source/menu.h b/source/menu.h new file mode 100644 index 0000000..6e2c9ef --- /dev/null +++ b/source/menu.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_MENU_H +#define _QUAKE_MENU_H + +enum m_state_e { + m_none, + m_main, + m_singleplayer, + m_load, + m_save, + m_multiplayer, + m_setup, + m_net, + m_options, + m_video, + m_keys, + m_help, + m_quit, + m_lanconfig, + m_gameoptions, + m_search, + m_slist +}; + +extern enum m_state_e m_state; +extern enum m_state_e m_return_state; + +extern qboolean m_entersound; + +// +// menus +// +void M_Init (void); +void M_Keydown (int key); +void M_Charinput (int key); +qboolean M_TextEntry (void); +void M_ToggleMenu_f (void); + +void M_Menu_Main_f (void); +void M_Menu_Options_f (void); +void M_Menu_Quit_f (void); + +void M_Print (int cx, int cy, const char *str); +void M_PrintWhite (int cx, int cy, const char *str); + +void M_Draw (void); +void M_DrawCharacter (int cx, int line, int num); + +void M_DrawPic (int x, int y, qpic_t *pic); +void M_DrawTransPic (int x, int y, qpic_t *pic); +void M_DrawCheckbox (int x, int y, int on); + +#endif /* _QUAKE_MENU_H */ + diff --git a/source/modelgen.h b/source/modelgen.h new file mode 100644 index 0000000..92dbcf4 --- /dev/null +++ b/source/modelgen.h @@ -0,0 +1,141 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +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 _MODELGEN_H +#define _MODELGEN_H + +// +// 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. * +// ********************************************************* + +#ifdef INCLUDELIBS + +#include +#include +#include +#include + +#include "cmdlib.h" +#include "scriplib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "mathlib.h" + +#endif + +#define ALIAS_VERSION 6 + +#define ALIAS_ONSEAM 0x0020 + +// must match definition in spritegn.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum {ST_SYNC=0, ST_RAND } synctype_t; +#endif + +typedef enum { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t; + +typedef enum { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t; + +typedef struct { + 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 { + 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 { + byte v[3]; + byte lightnormalindex; +} trivertx_t; + +typedef struct { + 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 { + int numframes; + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used +} daliasgroup_t; + +typedef struct { + int numskins; +} daliasskingroup_t; + +typedef struct { + float interval; +} daliasinterval_t; + +typedef struct { + float interval; +} daliasskininterval_t; + +typedef struct { + aliasframetype_t type; +} daliasframetype_t; + +typedef struct { + aliasskintype_t type; +} daliasskintype_t; + +#define IDPOLYHEADER (('O'<<24)+('P'<<16)+('D'<<8)+'I') + // little-endian "IDPO" + +#endif /* _MODELGEN_H */ + diff --git a/source/net.h b/source/net.h new file mode 100644 index 0000000..a078490 --- /dev/null +++ b/source/net.h @@ -0,0 +1,115 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2009-2010 Ozkan Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ + +/* + net.h + quake's interface to the networking layer + network functions and data, common to the + whole engine +*/ + +#ifndef _QUAKE_NET_H +#define _QUAKE_NET_H + + +#define NET_NAMELEN 64 + +#define NET_MAXMESSAGE 64000 /* ericw -- was 32000 */ + +extern int DEFAULTnet_hostport; +extern int net_hostport; + +extern cvar_t hostname; + +extern double net_time; +extern sizebuf_t net_message; +extern int net_activeconnections; + + +void NET_Init (void); +void NET_Shutdown (void); + +struct qsocket_s *NET_CheckNewConnections (void); +// returns a new connection number if there is one pending, else -1 + +struct qsocket_s *NET_Connect (const char *host); +// called by client to connect to a host. Returns -1 if not able to + +double NET_QSocketGetTime (const struct qsocket_s *sock); +const char *NET_QSocketGetAddressString (const struct qsocket_s *sock); + +qboolean NET_CanSendMessage (struct qsocket_s *sock); +// Returns true or false if the given qsocket can currently accept a +// message to be transmitted. + +int NET_GetMessage (struct qsocket_s *sock); +// returns data in net_message sizebuf +// returns 0 if no data is waiting +// returns 1 if a message was received +// returns 2 if an unreliable message was received +// returns -1 if the connection died + +int NET_SendMessage (struct qsocket_s *sock, sizebuf_t *data); +int NET_SendUnreliableMessage (struct qsocket_s *sock, sizebuf_t *data); +// returns 0 if the message connot be delivered reliably, but the connection +// is still considered valid +// returns 1 if the message was sent properly +// returns -1 if the connection died + +int NET_SendToAll(sizebuf_t *data, double blocktime); +// This is a reliable *blocking* send to all attached clients. + +void NET_Close (struct qsocket_s *sock); +// if a dead connection is returned by a get or send function, this function +// should be called when it is convenient + +// Server calls when a client is kicked off for a game related misbehavior +// like an illegal protocal conversation. Client calls when disconnecting +// from a server. +// A netcon_t number will not be reused until this function is called for it + +void NET_Poll (void); + + +// Server list related globals: +extern qboolean slistInProgress; +extern qboolean slistSilent; +extern qboolean slistLocal; + +extern int hostCacheCount; + +void NET_Slist_f (void); +void NET_SlistSort (void); +const char *NET_SlistPrintServer (int n); +const char *NET_SlistPrintServerName (int n); + + +/* FIXME: driver related, but public: + */ +extern qboolean ipxAvailable; +extern qboolean tcpipAvailable; +extern char my_ipx_address[NET_NAMELEN]; +extern char my_tcpip_address[NET_NAMELEN]; + +#endif /* _QUAKE_NET_H */ + diff --git a/source/net_bsd.c b/source/net_bsd.c new file mode 100644 index 0000000..59487e7 --- /dev/null +++ b/source/net_bsd.c @@ -0,0 +1,97 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +#include "net_dgrm.h" +#include "net_loop.h" + +net_driver_t net_drivers[] = +{ + { "Loopback", + false, + Loop_Init, + Loop_Listen, + Loop_SearchForHosts, + Loop_Connect, + Loop_CheckNewConnections, + Loop_GetMessage, + Loop_SendMessage, + Loop_SendUnreliableMessage, + Loop_CanSendMessage, + Loop_CanSendUnreliableMessage, + Loop_Close, + Loop_Shutdown + }, + + { "Datagram", + false, + Datagram_Init, + Datagram_Listen, + Datagram_SearchForHosts, + Datagram_Connect, + Datagram_CheckNewConnections, + Datagram_GetMessage, + Datagram_SendMessage, + Datagram_SendUnreliableMessage, + Datagram_CanSendMessage, + Datagram_CanSendUnreliableMessage, + Datagram_Close, + Datagram_Shutdown + } +}; + +const int net_numdrivers = (sizeof(net_drivers) / sizeof(net_drivers[0])); + +#include "net_udp.h" + +net_landriver_t net_landrivers[] = +{ + { "UDP", + false, + 0, + UDP_Init, + UDP_Shutdown, + UDP_Listen, + UDP_OpenSocket, + UDP_CloseSocket, + UDP_Connect, + UDP_CheckNewConnections, + UDP_Read, + UDP_Write, + UDP_Broadcast, + UDP_AddrToString, + UDP_StringToAddr, + UDP_GetSocketAddr, + UDP_GetNameFromAddr, + UDP_GetAddrFromName, + UDP_AddrCompare, + UDP_GetSocketPort, + UDP_SetSocketPort + } +}; + +const int net_numlandrivers = (sizeof(net_landrivers) / sizeof(net_landrivers[0])); + diff --git a/source/net_defs.h b/source/net_defs.h new file mode 100644 index 0000000..424b85d --- /dev/null +++ b/source/net_defs.h @@ -0,0 +1,260 @@ +/* + * net_defs.h -- functions and data private to the network layer + * net_sys.h and its dependencies must be included before net_defs.h. + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Copyright (C) 2005-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NET_DEFS_H +#define __NET_DEFS_H + +struct qsockaddr +{ +#if defined(HAVE_SA_LEN) + unsigned char qsa_len; + unsigned char qsa_family; +#else + short qsa_family; +#endif /* BSD, sockaddr */ + unsigned char qsa_data[14]; +}; + +#define NET_HEADERSIZE (2 * sizeof(unsigned int)) +#define NET_DATAGRAMSIZE (MAX_DATAGRAM + NET_HEADERSIZE) + +// 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 + +#if (NETFLAG_LENGTH_MASK & NET_MAXMESSAGE) != NET_MAXMESSAGE +#error "NET_MAXMESSAGE must fit within NETFLAG_LENGTH_MASK" +#endif + +#define NET_PROTOCOL_VERSION 3 + +/** + +This is 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: + 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 + +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 + + 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 CCREP_ACCEPT 0x81 +#define CCREP_REJECT 0x82 +#define CCREP_SERVER_INFO 0x83 +#define CCREP_PLAYER_INFO 0x84 +#define CCREP_RULE_INFO 0x85 + +typedef struct qsocket_s +{ + struct qsocket_s *next; + double connecttime; + double lastMessageTime; + double lastSendTime; + + qboolean disconnected; + qboolean canSend; + qboolean sendNext; + + int driver; + int landriver; + sys_socket_t socket; + void *driverdata; + + unsigned int ackSequence; + unsigned int sendSequence; + unsigned int unreliableSendSequence; + int sendMessageLength; + byte sendMessage [NET_MAXMESSAGE]; + + unsigned int receiveSequence; + unsigned int unreliableReceiveSequence; + int receiveMessageLength; + byte receiveMessage [NET_MAXMESSAGE]; + + struct qsockaddr addr; + char address[NET_NAMELEN]; + +} qsocket_t; + +extern qsocket_t *net_activeSockets; +extern qsocket_t *net_freeSockets; +extern int net_numsockets; + +typedef struct +{ + const char *name; + qboolean initialized; + sys_socket_t controlSock; + sys_socket_t (*Init) (void); + void (*Shutdown) (void); + void (*Listen) (qboolean state); + sys_socket_t (*Open_Socket) (int port); + int (*Close_Socket) (sys_socket_t socketid); + int (*Connect) (sys_socket_t socketid, struct qsockaddr *addr); + sys_socket_t (*CheckNewConnections) (void); + int (*Read) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); + int (*Write) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); + int (*Broadcast) (sys_socket_t socketid, byte *buf, int len); + const char * (*AddrToString) (struct qsockaddr *addr); + int (*StringToAddr) (const char *string, struct qsockaddr *addr); + int (*GetSocketAddr) (sys_socket_t socketid, struct qsockaddr *addr); + int (*GetNameFromAddr) (struct qsockaddr *addr, char *name); + int (*GetAddrFromName) (const char *name, struct qsockaddr *addr); + int (*AddrCompare) (struct qsockaddr *addr1, struct qsockaddr *addr2); + int (*GetSocketPort) (struct qsockaddr *addr); + int (*SetSocketPort) (struct qsockaddr *addr, int port); +} net_landriver_t; + +#define MAX_NET_DRIVERS 8 +extern net_landriver_t net_landrivers[]; +extern const int net_numlandrivers; + +typedef struct +{ + const char *name; + qboolean initialized; + int (*Init) (void); + void (*Listen) (qboolean state); + void (*SearchForHosts) (qboolean xmit); + qsocket_t *(*Connect) (const char *host); + qsocket_t *(*CheckNewConnections) (void); + int (*QGetMessage) (qsocket_t *sock); + int (*QSendMessage) (qsocket_t *sock, sizebuf_t *data); + int (*SendUnreliableMessage) (qsocket_t *sock, sizebuf_t *data); + qboolean (*CanSendMessage) (qsocket_t *sock); + qboolean (*CanSendUnreliableMessage) (qsocket_t *sock); + void (*Close) (qsocket_t *sock); + void (*Shutdown) (void); +} net_driver_t; + +extern net_driver_t net_drivers[]; +extern const int net_numdrivers; + +/* Loop driver must always be registered the first */ +#define IS_LOOP_DRIVER(p) ((p) == 0) + +extern int net_driverlevel; + +extern int messagesSent; +extern int messagesReceived; +extern int unreliableMessagesSent; +extern int unreliableMessagesReceived; + +qsocket_t *NET_NewQSocket (void); +void NET_FreeQSocket(qsocket_t *); +double SetNetTime(void); + + +#define HOSTCACHESIZE 8 + +typedef struct +{ + char name[16]; + char map[16]; + char cname[32]; + int users; + int maxusers; + int driver; + int ldriver; + struct qsockaddr addr; +} hostcache_t; + +extern int hostCacheCount; +extern hostcache_t hostcache[HOSTCACHESIZE]; + + +typedef struct _PollProcedure +{ + struct _PollProcedure *next; + double nextTime; + void (*procedure)(void *); + void *arg; +} PollProcedure; + +void SchedulePollProcedure(PollProcedure *pp, double timeOffset); + +#endif /* __NET_DEFS_H */ + diff --git a/source/net_dgrm.c b/source/net_dgrm.c new file mode 100644 index 0000000..4a4b4f7 --- /dev/null +++ b/source/net_dgrm.c @@ -0,0 +1,1421 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 enables a simple IP banning mechanism +#define BAN_TEST + +#include "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" +#include "net_dgrm.h" + +// these two macros are to make the code more readable +#define sfunc net_landrivers[sock->landriver] +#define dfunc net_landrivers[net_landriverlevel] + +static int net_landriverlevel; + +/* statistic counters */ +static int packetsSent = 0; +static int packetsReSent = 0; +static int packetsReceived = 0; +static int receivedDuplicateCount = 0; +static int shortPacketCount = 0; +static int droppedDatagrams; + +static struct +{ + unsigned int length; + unsigned int sequence; + byte data[MAX_DATAGRAM]; +} packetBuffer; + +static int myDriverLevel; + +extern qboolean m_return_onerror; +extern char m_return_reason[32]; + + +static char *StrAddr (struct qsockaddr *addr) +{ + static char buf[34]; + byte *p = (byte *)addr; + int n; + + for (n = 0; n < 16; n++) + sprintf (buf + n * 2, "%02x", *p++); + return buf; +} + + +#ifdef BAN_TEST + +static struct in_addr banAddr; +static struct in_addr banMask; + +static void NET_Ban_f (void) +{ + char addrStr [32]; + char maskStr [32]; + void (*print_fn)(const char *fmt, ...) FUNCP_PRINTF(1,2); + + if (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + print_fn = Con_Printf; + } + else + { + if (pr_global_struct->deathmatch) + return; + print_fn = SV_ClientPrintf; + } + + switch (Cmd_Argc ()) + { + case 1: + if (banAddr.s_addr != INADDR_ANY) + { + Q_strcpy(addrStr, inet_ntoa(banAddr)); + Q_strcpy(maskStr, inet_ntoa(banMask)); + print_fn("Banning %s [%s]\n", addrStr, maskStr); + } + else + print_fn("Banning not active\n"); + break; + + case 2: + if (q_strcasecmp(Cmd_Argv(1), "off") == 0) + banAddr.s_addr = INADDR_ANY; + else + banAddr.s_addr = inet_addr(Cmd_Argv(1)); + banMask.s_addr = INADDR_NONE; + break; + + case 3: + banAddr.s_addr = inet_addr(Cmd_Argv(1)); + banMask.s_addr = inet_addr(Cmd_Argv(2)); + break; + + default: + print_fn("BAN ip_address [mask]\n"); + break; + } +} +#endif // BAN_TEST + + +int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + +#ifdef DEBUG + if (data->cursize == 0) + Sys_Error("Datagram_SendMessage: zero length message\n"); + + if (data->cursize > NET_MAXMESSAGE) + Sys_Error("Datagram_SendMessage: message too big %u\n", data->cursize); + + if (sock->canSend == false) + Sys_Error("SendMessage: called with canSend == false\n"); +#endif + + Q_memcpy(sock->sendMessage, data->data, data->cursize); + sock->sendMessageLength = data->cursize; + + if (data->cursize <= MAX_DATAGRAM) + { + dataLen = data->cursize; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence++); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->canSend = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsSent++; + return 1; +} + + +static int SendMessageNext (qsocket_t *sock) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + if (sock->sendMessageLength <= MAX_DATAGRAM) + { + dataLen = sock->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence++); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->sendNext = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsSent++; + return 1; +} + + +static int ReSendMessage (qsocket_t *sock) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + if (sock->sendMessageLength <= MAX_DATAGRAM) + { + dataLen = sock->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence - 1); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->sendNext = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsReSent++; + return 1; +} + + +qboolean Datagram_CanSendMessage (qsocket_t *sock) +{ + if (sock->sendNext) + SendMessageNext (sock); + + return sock->canSend; +} + + +qboolean Datagram_CanSendUnreliableMessage (qsocket_t *sock) +{ + return true; +} + + +int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + int packetLen; + +#ifdef DEBUG + if (data->cursize == 0) + Sys_Error("Datagram_SendUnreliableMessage: zero length message\n"); + + if (data->cursize > MAX_DATAGRAM) + Sys_Error("Datagram_SendUnreliableMessage: message too big %u\n", data->cursize); +#endif + + packetLen = NET_HEADERSIZE + data->cursize; + + packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE); + packetBuffer.sequence = BigLong(sock->unreliableSendSequence++); + Q_memcpy (packetBuffer.data, data->data, data->cursize); + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + packetsSent++; + return 1; +} + + +int Datagram_GetMessage (qsocket_t *sock) +{ + unsigned int length; + unsigned int flags; + int ret = 0; + struct qsockaddr readaddr; + unsigned int sequence; + unsigned int count; + + if (!sock->canSend) + if ((net_time - sock->lastSendTime) > 1.0) + ReSendMessage (sock); + + while (1) + { + length = (unsigned int) sfunc.Read(sock->socket, (byte *)&packetBuffer, + NET_DATAGRAMSIZE, &readaddr); + + // if ((rand() & 255) > 220) + // continue; + + if (length == 0) + break; + + if (length == (unsigned int)-1) + { + Con_Printf("Read error\n"); + return -1; + } + + if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0) + { + Con_Printf("Forged packet received\n"); + Con_Printf("Expected: %s\n", StrAddr (&sock->addr)); + Con_Printf("Received: %s\n", StrAddr (&readaddr)); + continue; + } + + if (length < NET_HEADERSIZE) + { + shortPacketCount++; + continue; + } + + length = BigLong(packetBuffer.length); + flags = length & (~NETFLAG_LENGTH_MASK); + length &= NETFLAG_LENGTH_MASK; + + if (flags & NETFLAG_CTL) + continue; + + sequence = BigLong(packetBuffer.sequence); + packetsReceived++; + + if (flags & NETFLAG_UNRELIABLE) + { + if (sequence < sock->unreliableReceiveSequence) + { + Con_DPrintf("Got a stale datagram\n"); + ret = 0; + break; + } + if (sequence != sock->unreliableReceiveSequence) + { + count = sequence - sock->unreliableReceiveSequence; + droppedDatagrams += count; + Con_DPrintf("Dropped %u datagram(s)\n", count); + } + sock->unreliableReceiveSequence = sequence + 1; + + length -= NET_HEADERSIZE; + + SZ_Clear (&net_message); + SZ_Write (&net_message, packetBuffer.data, length); + + ret = 2; + break; + } + + if (flags & NETFLAG_ACK) + { + if (sequence != (sock->sendSequence - 1)) + { + Con_DPrintf("Stale ACK received\n"); + continue; + } + if (sequence == sock->ackSequence) + { + sock->ackSequence++; + if (sock->ackSequence != sock->sendSequence) + Con_DPrintf("ack sequencing error\n"); + } + else + { + Con_DPrintf("Duplicate ACK received\n"); + continue; + } + sock->sendMessageLength -= MAX_DATAGRAM; + if (sock->sendMessageLength > 0) + { + memmove (sock->sendMessage, sock->sendMessage + MAX_DATAGRAM, sock->sendMessageLength); + sock->sendNext = true; + } + else + { + sock->sendMessageLength = 0; + sock->canSend = true; + } + continue; + } + + if (flags & NETFLAG_DATA) + { + packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK); + packetBuffer.sequence = BigLong(sequence); + sfunc.Write (sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &readaddr); + + if (sequence != sock->receiveSequence) + { + receivedDuplicateCount++; + continue; + } + sock->receiveSequence++; + + length -= NET_HEADERSIZE; + + if (flags & NETFLAG_EOM) + { + SZ_Clear(&net_message); + SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength); + SZ_Write(&net_message, packetBuffer.data, length); + sock->receiveMessageLength = 0; + + ret = 1; + break; + } + + Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length); + sock->receiveMessageLength += length; + continue; + } + } + + if (sock->sendNext) + SendMessageNext (sock); + + return ret; +} + + +static void PrintStats(qsocket_t *s) +{ + Con_Printf("canSend = %4u \n", s->canSend); + Con_Printf("sendSeq = %4u ", s->sendSequence); + Con_Printf("recvSeq = %4u \n", s->receiveSequence); + Con_Printf("\n"); +} + +static void NET_Stats_f (void) +{ + qsocket_t *s; + + if (Cmd_Argc () == 1) + { + Con_Printf("unreliable messages sent = %i\n", unreliableMessagesSent); + Con_Printf("unreliable messages recv = %i\n", unreliableMessagesReceived); + Con_Printf("reliable messages sent = %i\n", messagesSent); + Con_Printf("reliable messages received = %i\n", messagesReceived); + 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("shortPacketCount = %i\n", shortPacketCount); + Con_Printf("droppedDatagrams = %i\n", droppedDatagrams); + } + else if (Q_strcmp(Cmd_Argv(1), "*") == 0) + { + for (s = net_activeSockets; s; s = s->next) + PrintStats(s); + for (s = net_freeSockets; s; s = s->next) + PrintStats(s); + } + else + { + for (s = net_activeSockets; s; s = s->next) + { + if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + break; + } + + if (s == NULL) + { + for (s = net_freeSockets; s; s = s->next) + { + if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + break; + } + } + + if (s == NULL) + return; + + PrintStats(s); + } +} + + +// recognize ip:port (based on ProQuake) +static const char *Strip_Port (const char *host) +{ + static char noport[MAX_QPATH]; + /* array size as in Host_Connect_f() */ + char *p; + int port; + + if (!host || !*host) + return host; + q_strlcpy (noport, host, sizeof(noport)); + if ((p = Q_strrchr(noport, ':')) == NULL) + return host; + *p++ = '\0'; + port = Q_atoi (p); + if (port > 0 && port < 65536 && port != net_hostport) + { + net_hostport = port; + Con_Printf("Port set to %d\n", net_hostport); + } + return noport; +} + + +static qboolean testInProgress = false; +static int testPollCount; +static int testDriver; +static sys_socket_t testSocket; + +static void Test_Poll (void *); +static PollProcedure testPollProcedure = {NULL, 0.0, Test_Poll}; + +static void Test_Poll (void *unused) +{ + struct qsockaddr clientaddr; + int control; + int len; + char name[32]; + char address[64]; + int colors; + int frags; + int connectTime; + + net_landriverlevel = testDriver; + + while (1) + { + len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + break; + + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + break; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + break; + if ((control & NETFLAG_LENGTH_MASK) != len) + break; + + if (MSG_ReadByte() != CCREP_PLAYER_INFO) + Sys_Error("Unexpected repsonse to Player Info request\n"); + + MSG_ReadByte(); /* playerNumber */ + Q_strcpy(name, MSG_ReadString()); + colors = MSG_ReadLong(); + frags = MSG_ReadLong(); + connectTime = MSG_ReadLong(); + Q_strcpy(address, MSG_ReadString()); + + Con_Printf("%s\n frags:%3i colors:%d %d time:%d\n %s\n", name, frags, colors >> 4, colors & 0x0f, connectTime / 60, address); + } + + testPollCount--; + if (testPollCount) + { + SchedulePollProcedure(&testPollProcedure, 0.1); + } + else + { + dfunc.Close_Socket(testSocket); + testInProgress = false; + } +} + +static void Test_f (void) +{ + const char *host; + int n; + int maxusers = MAX_SCOREBOARD; + struct qsockaddr sendaddr; + + if (testInProgress) + return; + + host = Strip_Port (Cmd_Argv(1)); + + if (host && hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + if (hostcache[n].driver != myDriverLevel) + continue; + net_landriverlevel = hostcache[n].ldriver; + maxusers = hostcache[n].maxusers; + Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr)); + break; + } + } + if (n < hostCacheCount) + goto JustDoIt; + } + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (!net_landrivers[net_landriverlevel].initialized) + continue; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) != -1) + break; + } + + if (net_landriverlevel == net_numlandrivers) + { + Con_Printf("Could not resolve %s\n", host); + return; + } + +JustDoIt: + testSocket = dfunc.Open_Socket(0); + if (testSocket == INVALID_SOCKET) + return; + + testInProgress = true; + testPollCount = 20; + testDriver = net_landriverlevel; + + for (n = 0; n < maxusers; n++) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO); + MSG_WriteByte(&net_message, n); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (testSocket, net_message.data, net_message.cursize, &sendaddr); + } + SZ_Clear(&net_message); + SchedulePollProcedure(&testPollProcedure, 0.1); +} + + +static qboolean test2InProgress = false; +static int test2Driver; +static sys_socket_t test2Socket; + +static void Test2_Poll (void *); +static PollProcedure test2PollProcedure = {NULL, 0.0, Test2_Poll}; + +static void Test2_Poll (void *unused) +{ + struct qsockaddr clientaddr; + int control; + int len; + char name[256]; + char value[256]; + + net_landriverlevel = test2Driver; + name[0] = 0; + + len = dfunc.Read (test2Socket, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + goto Reschedule; + + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + goto Error; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + goto Error; + if ((control & NETFLAG_LENGTH_MASK) != len) + goto Error; + + if (MSG_ReadByte() != CCREP_RULE_INFO) + goto Error; + + Q_strcpy(name, MSG_ReadString()); + if (name[0] == 0) + goto Done; + Q_strcpy(value, MSG_ReadString()); + + Con_Printf("%-16.16s %-16.16s\n", name, value); + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_RULE_INFO); + MSG_WriteString(&net_message, name); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (test2Socket, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + +Reschedule: + SchedulePollProcedure(&test2PollProcedure, 0.05); + return; + +Error: + Con_Printf("Unexpected repsonse to Rule Info request\n"); +Done: + dfunc.Close_Socket(test2Socket); + test2InProgress = false; + return; +} + +static void Test2_f (void) +{ + const char *host; + int n; + struct qsockaddr sendaddr; + + if (test2InProgress) + return; + + host = Strip_Port (Cmd_Argv(1)); + + if (host && hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + if (hostcache[n].driver != myDriverLevel) + continue; + net_landriverlevel = hostcache[n].ldriver; + Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr)); + break; + } + } + + if (n < hostCacheCount) + goto JustDoIt; + } + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (!net_landrivers[net_landriverlevel].initialized) + continue; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) != -1) + break; + } + + if (net_landriverlevel == net_numlandrivers) + { + Con_Printf("Could not resolve %s\n", host); + return; + } + +JustDoIt: + test2Socket = dfunc.Open_Socket(0); + if (test2Socket == INVALID_SOCKET) + return; + + test2InProgress = true; + test2Driver = net_landriverlevel; + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_RULE_INFO); + MSG_WriteString(&net_message, ""); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (test2Socket, net_message.data, net_message.cursize, &sendaddr); + SZ_Clear(&net_message); + SchedulePollProcedure(&test2PollProcedure, 0.05); +} + + +int Datagram_Init (void) +{ + int i, num_inited; + sys_socket_t csock; + +#ifdef BAN_TEST + banAddr.s_addr = INADDR_ANY; + banMask.s_addr = INADDR_NONE; +#endif + myDriverLevel = net_driverlevel; + + Cmd_AddCommand ("net_stats", NET_Stats_f); + + if (safemode || COM_CheckParm("-nolan")) + return -1; + + num_inited = 0; + for (i = 0; i < net_numlandrivers; i++) + { + csock = net_landrivers[i].Init (); + if (csock == INVALID_SOCKET) + continue; + net_landrivers[i].initialized = true; + net_landrivers[i].controlSock = csock; + num_inited++; + } + + if (num_inited == 0) + return -1; + +#ifdef BAN_TEST + Cmd_AddCommand ("ban", NET_Ban_f); +#endif + Cmd_AddCommand ("test", Test_f); + Cmd_AddCommand ("test2", Test2_f); + + return 0; +} + + +void Datagram_Shutdown (void) +{ + int i; + +// +// shutdown the lan drivers +// + for (i = 0; i < net_numlandrivers; i++) + { + if (net_landrivers[i].initialized) + { + net_landrivers[i].Shutdown (); + net_landrivers[i].initialized = false; + } + } +} + + +void Datagram_Close (qsocket_t *sock) +{ + sfunc.Close_Socket(sock->socket); +} + + +void Datagram_Listen (qboolean state) +{ + int i; + + for (i = 0; i < net_numlandrivers; i++) + { + if (net_landrivers[i].initialized) + net_landrivers[i].Listen (state); + } +} + + +static qsocket_t *_Datagram_CheckNewConnections (void) +{ + struct qsockaddr clientaddr; + struct qsockaddr newaddr; + sys_socket_t newsock; + sys_socket_t acceptsock; + qsocket_t *sock; + qsocket_t *s; + int len; + int command; + int control; + int ret; + + acceptsock = dfunc.CheckNewConnections(); + if (acceptsock == INVALID_SOCKET) + return NULL; + + SZ_Clear(&net_message); + + len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + return NULL; + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + return NULL; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + return NULL; + if ((control & NETFLAG_LENGTH_MASK) != len) + return NULL; + + command = MSG_ReadByte(); + if (command == CCREQ_SERVER_INFO) + { + if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) + return NULL; + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_SERVER_INFO); + dfunc.GetSocketAddr(acceptsock, &newaddr); + MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + MSG_WriteString(&net_message, hostname.string); + MSG_WriteString(&net_message, sv.name); + MSG_WriteByte(&net_message, net_activeconnections); + MSG_WriteByte(&net_message, svs.maxclients); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + + if (command == CCREQ_PLAYER_INFO) + { + int playerNumber; + int activeNumber; + int clientNumber; + client_t *client; + + playerNumber = MSG_ReadByte(); + activeNumber = -1; + + for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) + { + if (client->active) + { + activeNumber++; + if (activeNumber == playerNumber) + break; + } + } + + if (clientNumber == svs.maxclients) + return NULL; + + 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, (int)client->edict->v.frags); + MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime)); + MSG_WriteString(&net_message, client->netconnection->address); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return NULL; + } + + if (command == CCREQ_RULE_INFO) + { + const char *prevCvarName; + cvar_t *var; + + // find the search start location + prevCvarName = MSG_ReadString(); + var = Cvar_FindVarAfter (prevCvarName, CVAR_SERVERINFO); + + // 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); + } + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return NULL; + } + + if (command != CCREQ_CONNECT) + return NULL; + + if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) + return NULL; + + if (MSG_ReadByte() != NET_PROTOCOL_VERSION) + { + 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"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + +#ifdef BAN_TEST + // check for a ban + if (clientaddr.qsa_family == AF_INET) + { + in_addr_t testAddr; + testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr; + if ((testAddr & banMask.s_addr) == banAddr.s_addr) + { + 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, "You have been banned.\n"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + } +#endif + + // see if this guy is already connected + for (s = net_activeSockets; s; s = s->next) + { + if (s->driver != net_driverlevel) + continue; + ret = dfunc.AddrCompare(&clientaddr, &s->addr); + if (ret >= 0) + { + // is this a duplicate connection reqeust? + if (ret == 0 && net_time - s->connecttime < 2.0) + { + // yes, so send a duplicate reply + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + dfunc.GetSocketAddr(s->socket, &newaddr); + MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + // it's somebody coming back in from a crash/disconnect + // so close the old qsocket and let their retry get them back in + NET_Close(s); + return NULL; + } + } + + // allocate a QSocket + sock = NET_NewQSocket (); + if (sock == NULL) + { + // no room; try to let him 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"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + + // allocate a network socket + newsock = dfunc.Open_Socket(0); + if (newsock == INVALID_SOCKET) + { + NET_FreeQSocket(sock); + return NULL; + } + + // connect to the client + if (dfunc.Connect (newsock, &clientaddr) == -1) + { + dfunc.Close_Socket(newsock); + NET_FreeQSocket(sock); + return NULL; + } + + // everything is allocated, just fill in the details + sock->socket = newsock; + sock->landriver = net_landriverlevel; + sock->addr = clientaddr; + Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr)); + + // send him back the info about the server connection he has been allocated + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + dfunc.GetSocketAddr(newsock, &newaddr); + MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); +// MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return sock; +} + +qsocket_t *Datagram_CheckNewConnections (void) +{ + qsocket_t *ret = NULL; + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (net_landrivers[net_landriverlevel].initialized) + { + if ((ret = _Datagram_CheckNewConnections ()) != NULL) + break; + } + } + return ret; +} + + +static void _Datagram_SearchForHosts (qboolean xmit) +{ + int ret; + int n; + int i; + struct qsockaddr readaddr; + struct qsockaddr myaddr; + int control; + + dfunc.GetSocketAddr (dfunc.controlSock, &myaddr); + if (xmit) + { + 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); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize); + SZ_Clear(&net_message); + } + + while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0) + { + if (ret < (int) sizeof(int)) + continue; + net_message.cursize = ret; + + // don't answer our own query + if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0) + continue; + + // is the cache full? + if (hostCacheCount == HOSTCACHESIZE) + continue; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + continue; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + continue; + if ((control & NETFLAG_LENGTH_MASK) != ret) + continue; + + if (MSG_ReadByte() != CCREP_SERVER_INFO) + continue; + + dfunc.GetAddrFromName(MSG_ReadString(), &readaddr); + // search the cache for this server + for (n = 0; n < hostCacheCount; n++) + { + if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0) + break; + } + + // is it already there? + if (n < hostCacheCount) + continue; + + // add it + hostCacheCount++; + Q_strcpy(hostcache[n].name, MSG_ReadString()); + Q_strcpy(hostcache[n].map, MSG_ReadString()); + hostcache[n].users = MSG_ReadByte(); + hostcache[n].maxusers = MSG_ReadByte(); + if (MSG_ReadByte() != NET_PROTOCOL_VERSION) + { + Q_strcpy(hostcache[n].cname, hostcache[n].name); + hostcache[n].cname[14] = 0; + Q_strcpy(hostcache[n].name, "*"); + Q_strcat(hostcache[n].name, hostcache[n].cname); + } + Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr)); + hostcache[n].driver = net_driverlevel; + hostcache[n].ldriver = net_landriverlevel; + Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr)); + + // check for a name conflict + for (i = 0; i < hostCacheCount; i++) + { + if (i == n) + continue; + if (q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0) + { + i = Q_strlen(hostcache[n].name); + if (i < 15 && hostcache[n].name[i-1] > '8') + { + hostcache[n].name[i] = '0'; + hostcache[n].name[i+1] = 0; + } + else + hostcache[n].name[i-1]++; + + i = -1; + } + } + } +} + +void Datagram_SearchForHosts (qboolean xmit) +{ + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (hostCacheCount == HOSTCACHESIZE) + break; + if (net_landrivers[net_landriverlevel].initialized) + _Datagram_SearchForHosts (xmit); + } +} + + +static qsocket_t *_Datagram_Connect (const char *host) +{ + struct qsockaddr sendaddr; + struct qsockaddr readaddr; + qsocket_t *sock; + sys_socket_t newsock; + int ret; + int reps; + double start_time; + int control; + const char *reason; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) == -1) + { + Con_Printf("Could not resolve %s\n", host); + return NULL; + } + + newsock = dfunc.Open_Socket (0); + if (newsock == INVALID_SOCKET) + return NULL; + + sock = NET_NewQSocket (); + if (sock == NULL) + goto ErrorReturn2; + sock->socket = newsock; + sock->landriver = net_landriverlevel; + + // connect to the host + if (dfunc.Connect (newsock, &sendaddr) == -1) + goto ErrorReturn; + + // send the connection request + Con_Printf("trying...\n"); + SCR_UpdateScreen (); + start_time = net_time; + + for (reps = 0; reps < 3; reps++) + { + 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); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr); + SZ_Clear(&net_message); + do + { + ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr); + // if we got something, validate it + if (ret > 0) + { + // is it from the right place? + if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) + { + Con_Printf("wrong reply address\n"); + Con_Printf("Expected: %s | %s\n", dfunc.AddrToString (&sendaddr), StrAddr(&sendaddr)); + Con_Printf("Received: %s | %s\n", dfunc.AddrToString (&readaddr), StrAddr(&readaddr)); + SCR_UpdateScreen (); + ret = 0; + continue; + } + + if (ret < (int) sizeof(int)) + { + ret = 0; + continue; + } + + net_message.cursize = ret; + MSG_BeginReading (); + + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + { + ret = 0; + continue; + } + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + { + ret = 0; + continue; + } + if ((control & NETFLAG_LENGTH_MASK) != ret) + { + ret = 0; + continue; + } + } + } + while (ret == 0 && (SetNetTime() - start_time) < 2.5); + + if (ret) + break; + + Con_Printf("still trying...\n"); + SCR_UpdateScreen (); + start_time = SetNetTime(); + } + + if (ret == 0) + { + reason = "No Response"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + if (ret == -1) + { + reason = "Network Error"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + ret = MSG_ReadByte(); + if (ret == CCREP_REJECT) + { + reason = MSG_ReadString(); + Con_Printf("%s\n", reason); + q_strlcpy(m_return_reason, reason, sizeof(m_return_reason)); + goto ErrorReturn; + } + + if (ret == CCREP_ACCEPT) + { + Q_memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr)); + dfunc.SetSocketPort (&sock->addr, MSG_ReadLong()); + } + else + { + reason = "Bad Response"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + dfunc.GetNameFromAddr (&sendaddr, sock->address); + + Con_Printf ("Connection accepted\n"); + sock->lastMessageTime = SetNetTime(); + + // switch the connection to the specified address + if (dfunc.Connect (newsock, &sock->addr) == -1) + { + reason = "Connect to Game failed"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + m_return_onerror = false; + return sock; + +ErrorReturn: + NET_FreeQSocket(sock); +ErrorReturn2: + dfunc.Close_Socket(newsock); + if (m_return_onerror) + { + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_return_state; + m_return_onerror = false; + } + return NULL; +} + +qsocket_t *Datagram_Connect (const char *host) +{ + qsocket_t *ret = NULL; + + host = Strip_Port (host); + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (net_landrivers[net_landriverlevel].initialized) + { + if ((ret = _Datagram_Connect (host)) != NULL) + break; + } + } + return ret; +} + diff --git a/source/net_dgrm.h b/source/net_dgrm.h new file mode 100644 index 0000000..357a829 --- /dev/null +++ b/source/net_dgrm.h @@ -0,0 +1,39 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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_DATAGRAM_H +#define __NET_DATAGRAM_H + +int Datagram_Init (void); +void Datagram_Listen (qboolean state); +void Datagram_SearchForHosts (qboolean xmit); +qsocket_t *Datagram_Connect (const char *host); +qsocket_t *Datagram_CheckNewConnections (void); +int Datagram_GetMessage (qsocket_t *sock); +int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data); +int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); +qboolean Datagram_CanSendMessage (qsocket_t *sock); +qboolean Datagram_CanSendUnreliableMessage (qsocket_t *sock); +void Datagram_Close (qsocket_t *sock); +void Datagram_Shutdown (void); + +#endif /* __NET_DATAGRAM_H */ + diff --git a/source/net_loop.c b/source/net_loop.c new file mode 100644 index 0000000..8cb22db --- /dev/null +++ b/source/net_loop.c @@ -0,0 +1,250 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" +#include "net_loop.h" + +static qboolean localconnectpending = false; +static qsocket_t *loop_client = NULL; +static qsocket_t *loop_server = NULL; + +int Loop_Init (void) +{ + if (cls.state == ca_dedicated) + return -1; + return 0; +} + + +void Loop_Shutdown (void) +{ +} + + +void Loop_Listen (qboolean state) +{ +} + + +void Loop_SearchForHosts (qboolean xmit) +{ + if (!sv.active) + return; + + hostCacheCount = 1; + if (Q_strcmp(hostname.string, "UNNAMED") == 0) + Q_strcpy(hostcache[0].name, "local"); + else + Q_strcpy(hostcache[0].name, hostname.string); + Q_strcpy(hostcache[0].map, sv.name); + hostcache[0].users = net_activeconnections; + hostcache[0].maxusers = svs.maxclients; + hostcache[0].driver = net_driverlevel; + Q_strcpy(hostcache[0].cname, "local"); +} + + +qsocket_t *Loop_Connect (const char *host) +{ + if (Q_strcmp(host,"local") != 0) + return NULL; + + localconnectpending = true; + + if (!loop_client) + { + if ((loop_client = NET_NewQSocket ()) == NULL) + { + Con_Printf("Loop_Connect: no qsocket available\n"); + return NULL; + } + Q_strcpy (loop_client->address, "localhost"); + } + loop_client->receiveMessageLength = 0; + loop_client->sendMessageLength = 0; + loop_client->canSend = true; + + if (!loop_server) + { + if ((loop_server = NET_NewQSocket ()) == NULL) + { + Con_Printf("Loop_Connect: no qsocket available\n"); + return NULL; + } + Q_strcpy (loop_server->address, "LOCAL"); + } + loop_server->receiveMessageLength = 0; + loop_server->sendMessageLength = 0; + loop_server->canSend = true; + + loop_client->driverdata = (void *)loop_server; + loop_server->driverdata = (void *)loop_client; + + return loop_client; +} + + +qsocket_t *Loop_CheckNewConnections (void) +{ + if (!localconnectpending) + return NULL; + + localconnectpending = false; + loop_server->sendMessageLength = 0; + loop_server->receiveMessageLength = 0; + loop_server->canSend = true; + loop_client->sendMessageLength = 0; + loop_client->receiveMessageLength = 0; + loop_client->canSend = true; + return loop_server; +} + + +static int IntAlign(int value) +{ + return (value + (sizeof(int) - 1)) & (~(sizeof(int) - 1)); +} + + +int Loop_GetMessage (qsocket_t *sock) +{ + int ret; + int length; + + if (sock->receiveMessageLength == 0) + return 0; + + ret = sock->receiveMessage[0]; + length = sock->receiveMessage[1] + (sock->receiveMessage[2] << 8); + // alignment byte skipped here + SZ_Clear (&net_message); + SZ_Write (&net_message, &sock->receiveMessage[4], length); + + length = IntAlign(length + 4); + sock->receiveMessageLength -= length; + + if (sock->receiveMessageLength) + memmove (sock->receiveMessage, &sock->receiveMessage[length], sock->receiveMessageLength); + + if (sock->driverdata && ret == 1) + ((qsocket_t *)sock->driverdata)->canSend = true; + + return ret; +} + + +int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + byte *buffer; + int *bufferLength; + + if (!sock->driverdata) + return -1; + + bufferLength = &((qsocket_t *)sock->driverdata)->receiveMessageLength; + + if ((*bufferLength + data->cursize + 4) > NET_MAXMESSAGE) + Sys_Error("Loop_SendMessage: overflow"); + + buffer = ((qsocket_t *)sock->driverdata)->receiveMessage + *bufferLength; + + // message type + *buffer++ = 1; + + // length + *buffer++ = data->cursize & 0xff; + *buffer++ = data->cursize >> 8; + + // align + buffer++; + + // message + Q_memcpy(buffer, data->data, data->cursize); + *bufferLength = IntAlign(*bufferLength + data->cursize + 4); + + sock->canSend = false; + return 1; +} + + +int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + byte *buffer; + int *bufferLength; + + if (!sock->driverdata) + return -1; + + bufferLength = &((qsocket_t *)sock->driverdata)->receiveMessageLength; + + if ((*bufferLength + data->cursize + sizeof(byte) + sizeof(short)) > NET_MAXMESSAGE) + return 0; + + buffer = ((qsocket_t *)sock->driverdata)->receiveMessage + *bufferLength; + + // message type + *buffer++ = 2; + + // length + *buffer++ = data->cursize & 0xff; + *buffer++ = data->cursize >> 8; + + // align + buffer++; + + // message + Q_memcpy(buffer, data->data, data->cursize); + *bufferLength = IntAlign(*bufferLength + data->cursize + 4); + return 1; +} + + +qboolean Loop_CanSendMessage (qsocket_t *sock) +{ + if (!sock->driverdata) + return false; + return sock->canSend; +} + + +qboolean Loop_CanSendUnreliableMessage (qsocket_t *sock) +{ + return true; +} + + +void Loop_Close (qsocket_t *sock) +{ + if (sock->driverdata) + ((qsocket_t *)sock->driverdata)->driverdata = NULL; + sock->receiveMessageLength = 0; + sock->sendMessageLength = 0; + sock->canSend = true; + if (sock == loop_client) + loop_client = NULL; + else + loop_server = NULL; +} + diff --git a/source/net_loop.h b/source/net_loop.h new file mode 100644 index 0000000..267193d --- /dev/null +++ b/source/net_loop.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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_LOOP_H +#define __NET_LOOP_H + +// net_loop.h +int Loop_Init (void); +void Loop_Listen (qboolean state); +void Loop_SearchForHosts (qboolean xmit); +qsocket_t *Loop_Connect (const char *host); +qsocket_t *Loop_CheckNewConnections (void); +int Loop_GetMessage (qsocket_t *sock); +int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data); +int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); +qboolean Loop_CanSendMessage (qsocket_t *sock); +qboolean Loop_CanSendUnreliableMessage (qsocket_t *sock); +void Loop_Close (qsocket_t *sock); +void Loop_Shutdown (void); + +#endif /* __NET_LOOP_H */ + diff --git a/source/net_main.c b/source/net_main.c new file mode 100644 index 0000000..bbd599a --- /dev/null +++ b/source/net_main.c @@ -0,0 +1,906 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +qsocket_t *net_activeSockets = NULL; +qsocket_t *net_freeSockets = NULL; +int net_numsockets = 0; + +qboolean ipxAvailable = false; +qboolean tcpipAvailable = false; + +int net_hostport; +int DEFAULTnet_hostport = 26000; + +char my_ipx_address[NET_NAMELEN]; +char my_tcpip_address[NET_NAMELEN]; + +static qboolean listening = false; + +qboolean slistInProgress = false; +qboolean slistSilent = false; +qboolean slistLocal = true; +static double slistStartTime; +static int slistLastShown; + +static void Slist_Send (void *); +static void Slist_Poll (void *); +static PollProcedure slistSendProcedure = {NULL, 0.0, Slist_Send}; +static PollProcedure slistPollProcedure = {NULL, 0.0, Slist_Poll}; + +sizebuf_t net_message; +int net_activeconnections = 0; + +int messagesSent = 0; +int messagesReceived = 0; +int unreliableMessagesSent = 0; +int unreliableMessagesReceived = 0; + +static cvar_t net_messagetimeout = {"net_messagetimeout","300",CVAR_NONE}; +cvar_t hostname = {"hostname", "UNNAMED", CVAR_NONE}; + +// these two macros are to make the code more readable +#define sfunc net_drivers[sock->driver] +#define dfunc net_drivers[net_driverlevel] + +int net_driverlevel; + +double net_time; + + +double SetNetTime (void) +{ + net_time = Sys_DoubleTime(); + return net_time; +} + + +/* +=================== +NET_NewQSocket + +Called by drivers when a new communications endpoint is required +The sequence and buffer fields will be filled in properly +=================== +*/ +qsocket_t *NET_NewQSocket (void) +{ + qsocket_t *sock; + + if (net_freeSockets == NULL) + return NULL; + + if (net_activeconnections >= svs.maxclients) + return NULL; + + // get one from free list + sock = net_freeSockets; + net_freeSockets = sock->next; + + // add it to active list + sock->next = net_activeSockets; + net_activeSockets = sock; + + sock->disconnected = false; + sock->connecttime = net_time; + Q_strcpy (sock->address,"UNSET ADDRESS"); + sock->driver = net_driverlevel; + sock->socket = 0; + sock->driverdata = NULL; + sock->canSend = true; + sock->sendNext = false; + sock->lastMessageTime = net_time; + sock->ackSequence = 0; + sock->sendSequence = 0; + sock->unreliableSendSequence = 0; + sock->sendMessageLength = 0; + sock->receiveSequence = 0; + sock->unreliableReceiveSequence = 0; + sock->receiveMessageLength = 0; + + return sock; +} + + +void NET_FreeQSocket(qsocket_t *sock) +{ + qsocket_t *s; + + // remove it from active list + if (sock == net_activeSockets) + net_activeSockets = net_activeSockets->next; + else + { + for (s = net_activeSockets; s; s = s->next) + { + if (s->next == sock) + { + s->next = sock->next; + break; + } + } + + if (!s) + Sys_Error ("NET_FreeQSocket: not active"); + } + + // add it to free list + sock->next = net_freeSockets; + net_freeSockets = sock; + sock->disconnected = true; +} + + +double NET_QSocketGetTime (const qsocket_t *s) +{ + return s->connecttime; +} + + +const char *NET_QSocketGetAddressString (const qsocket_t *s) +{ + return s->address; +} + + +static void NET_Listen_f (void) +{ + if (Cmd_Argc () != 2) + { + Con_Printf ("\"listen\" is \"%d\"\n", listening ? 1 : 0); + return; + } + + listening = Q_atoi(Cmd_Argv(1)) ? true : false; + + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.Listen (listening); + } +} + + +static void MaxPlayers_f (void) +{ + int n; + + if (Cmd_Argc () != 2) + { + Con_Printf ("\"maxplayers\" is \"%d\"\n", svs.maxclients); + return; + } + + if (sv.active) + { + Con_Printf ("maxplayers can not be changed while a server is running.\n"); + return; + } + + n = Q_atoi(Cmd_Argv(1)); + if (n < 1) + n = 1; + if (n > svs.maxclientslimit) + { + n = svs.maxclientslimit; + Con_Printf ("\"maxplayers\" set to \"%d\"\n", n); + } + + if ((n == 1) && listening) + Cbuf_AddText ("listen 0\n"); + + if ((n > 1) && (!listening)) + Cbuf_AddText ("listen 1\n"); + + svs.maxclients = n; + if (n == 1) + Cvar_Set ("deathmatch", "0"); + else + Cvar_Set ("deathmatch", "1"); +} + + +static void NET_Port_f (void) +{ + int n; + + if (Cmd_Argc () != 2) + { + Con_Printf ("\"port\" is \"%d\"\n", net_hostport); + return; + } + + n = Q_atoi(Cmd_Argv(1)); + if (n < 1 || n > 65534) + { + Con_Printf ("Bad value, must be between 1 and 65534\n"); + return; + } + + DEFAULTnet_hostport = n; + net_hostport = n; + + if (listening) + { + // force a change to the new port + Cbuf_AddText ("listen 0\n"); + Cbuf_AddText ("listen 1\n"); + } +} + + +static void PrintSlistHeader(void) +{ + Con_Printf("Server Map Users\n"); + Con_Printf("--------------- --------------- -----\n"); + slistLastShown = 0; +} + + +static void PrintSlist(void) +{ + int n; + + for (n = slistLastShown; n < hostCacheCount; n++) + { + if (hostcache[n].maxusers) + Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name, hostcache[n].map, hostcache[n].users, hostcache[n].maxusers); + else + Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name, hostcache[n].map); + } + slistLastShown = n; +} + + +static void PrintSlistTrailer(void) +{ + if (hostCacheCount) + Con_Printf("== end list ==\n\n"); + else + Con_Printf("No Quake servers found.\n\n"); +} + + +void NET_Slist_f (void) +{ + if (slistInProgress) + return; + + if (! slistSilent) + { + Con_Printf("Looking for Quake servers...\n"); + PrintSlistHeader(); + } + + slistInProgress = true; + slistStartTime = Sys_DoubleTime(); + + SchedulePollProcedure(&slistSendProcedure, 0.0); + SchedulePollProcedure(&slistPollProcedure, 0.1); + + hostCacheCount = 0; +} + + +void NET_SlistSort (void) +{ + if (hostCacheCount > 1) + { + int i, j; + hostcache_t temp; + for (i = 0; i < hostCacheCount; i++) + { + for (j = i + 1; j < hostCacheCount; j++) + { + if (strcmp(hostcache[j].name, hostcache[i].name) < 0) + { + memcpy(&temp, &hostcache[j], sizeof(hostcache_t)); + memcpy(&hostcache[j], &hostcache[i], sizeof(hostcache_t)); + memcpy(&hostcache[i], &temp, sizeof(hostcache_t)); + } + } + } + } +} + + +const char *NET_SlistPrintServer (int idx) +{ + static char string[64]; + + if (idx < 0 || idx >= hostCacheCount) + return ""; + + if (hostcache[idx].maxusers) + { + q_snprintf(string, sizeof(string), "%-15.15s %-15.15s %2u/%2u\n", + hostcache[idx].name, hostcache[idx].map, + hostcache[idx].users, hostcache[idx].maxusers); + } + else + { + q_snprintf(string, sizeof(string), "%-15.15s %-15.15s\n", + hostcache[idx].name, hostcache[idx].map); + } + + return string; +} + + +const char *NET_SlistPrintServerName (int idx) +{ + if (idx < 0 || idx >= hostCacheCount) + return ""; + return hostcache[idx].cname; +} + + +static void Slist_Send (void *unused) +{ + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + continue; + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.SearchForHosts (true); + } + + if ((Sys_DoubleTime() - slistStartTime) < 0.5) + SchedulePollProcedure(&slistSendProcedure, 0.75); +} + + +static void Slist_Poll (void *unused) +{ + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + continue; + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.SearchForHosts (false); + } + + if (! slistSilent) + PrintSlist(); + + if ((Sys_DoubleTime() - slistStartTime) < 1.5) + { + SchedulePollProcedure(&slistPollProcedure, 0.1); + return; + } + + if (! slistSilent) + PrintSlistTrailer(); + slistInProgress = false; + slistSilent = false; + slistLocal = true; +} + + +/* +=================== +NET_Connect +=================== +*/ + +int hostCacheCount = 0; +hostcache_t hostcache[HOSTCACHESIZE]; + +qsocket_t *NET_Connect (const char *host) +{ + qsocket_t *ret; + int n; + int numdrivers = net_numdrivers; + + SetNetTime(); + + if (host && *host == 0) + host = NULL; + + if (host) + { + if (q_strcasecmp (host, "local") == 0) + { + numdrivers = 1; + goto JustDoIt; + } + + if (hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + host = hostcache[n].cname; + break; + } + if (n < hostCacheCount) + goto JustDoIt; + } + } + + slistSilent = host ? true : false; + NET_Slist_f (); + + while (slistInProgress) + NET_Poll(); + + if (host == NULL) + { + if (hostCacheCount != 1) + return NULL; + host = hostcache[0].cname; + Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host); + } + + if (hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + host = hostcache[n].cname; + break; + } + } + } + +JustDoIt: + for (net_driverlevel = 0; net_driverlevel < numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + ret = dfunc.Connect (host); + if (ret) + return ret; + } + + if (host) + { + Con_Printf("\n"); + PrintSlistHeader(); + PrintSlist(); + PrintSlistTrailer(); + } + + return NULL; +} + + +/* +=================== +NET_CheckNewConnections +=================== +*/ +qsocket_t *NET_CheckNewConnections (void) +{ + qsocket_t *ret; + + SetNetTime(); + + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + if (!IS_LOOP_DRIVER(net_driverlevel) && listening == false) + continue; + ret = dfunc.CheckNewConnections (); + if (ret) + { + return ret; + } + } + + return NULL; +} + +/* +=================== +NET_Close +=================== +*/ +void NET_Close (qsocket_t *sock) +{ + if (!sock) + return; + + if (sock->disconnected) + return; + + SetNetTime(); + + // call the driver_Close function + sfunc.Close (sock); + + NET_FreeQSocket(sock); +} + + +/* +================= +NET_GetMessage + +If there is a complete message, return it in net_message + +returns 0 if no data is waiting +returns 1 if a message was received +returns -1 if connection is invalid +================= +*/ +int NET_GetMessage (qsocket_t *sock) +{ + int ret; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_GetMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + + ret = sfunc.QGetMessage(sock); + + // see if this connection has timed out + if (ret == 0 && !IS_LOOP_DRIVER(sock->driver)) + { + if (net_time - sock->lastMessageTime > net_messagetimeout.value) + { + NET_Close(sock); + return -1; + } + } + + if (ret > 0) + { + if (!IS_LOOP_DRIVER(sock->driver)) + { + sock->lastMessageTime = net_time; + if (ret == 1) + messagesReceived++; + else if (ret == 2) + unreliableMessagesReceived++; + } + } + + return ret; +} + + +/* +================== +NET_SendMessage + +Try to send a complete length+message unit over the reliable stream. +returns 0 if the message cannot be delivered reliably, but the connection + is still considered valid +returns 1 if the message was sent properly +returns -1 if the connection died +================== +*/ +int NET_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + int r; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_SendMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + r = sfunc.QSendMessage(sock, data); + if (r == 1 && !IS_LOOP_DRIVER(sock->driver)) + messagesSent++; + + return r; +} + + +int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + int r; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_SendMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + r = sfunc.SendUnreliableMessage(sock, data); + if (r == 1 && !IS_LOOP_DRIVER(sock->driver)) + unreliableMessagesSent++; + + return r; +} + + +/* +================== +NET_CanSendMessage + +Returns true or false if the given qsocket can currently accept a +message to be transmitted. +================== +*/ +qboolean NET_CanSendMessage (qsocket_t *sock) +{ + if (!sock) + return false; + + if (sock->disconnected) + return false; + + SetNetTime(); + + return sfunc.CanSendMessage(sock); +} + + +int NET_SendToAll (sizebuf_t *data, double blocktime) +{ + double start; + int i; + int count = 0; + qboolean msg_init[MAX_SCOREBOARD]; /* did we write the message to the client's connection */ + qboolean msg_sent[MAX_SCOREBOARD]; /* did the msg arrive its destination (canSend state). */ + + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + /* + if (!host_client->netconnection) + continue; + if (host_client->active) + */ + if (host_client->netconnection && host_client->active) + { + if (IS_LOOP_DRIVER(host_client->netconnection->driver)) + { + NET_SendMessage(host_client->netconnection, data); + msg_init[i] = true; + msg_sent[i] = true; + continue; + } + count++; + msg_init[i] = false; + msg_sent[i] = false; + } + else + { + msg_init[i] = true; + msg_sent[i] = true; + } + } + + start = Sys_DoubleTime(); + while (count) + { + count = 0; + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + if (! msg_init[i]) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + msg_init[i] = true; + NET_SendMessage(host_client->netconnection, data); + } + else + { + NET_GetMessage (host_client->netconnection); + } + count++; + continue; + } + + if (! msg_sent[i]) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + msg_sent[i] = true; + } + else + { + NET_GetMessage (host_client->netconnection); + } + count++; + continue; + } + } + if ((Sys_DoubleTime() - start) > blocktime) + break; + } + return count; +} + + +//============================================================================= + +/* +==================== +NET_Init +==================== +*/ + +void NET_Init (void) +{ + int i; + qsocket_t *s; + + i = COM_CheckParm ("-port"); + if (!i) + i = COM_CheckParm ("-udpport"); + if (!i) + i = COM_CheckParm ("-ipxport"); + + if (i) + { + if (i < com_argc-1) + DEFAULTnet_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("NET_Init: you must specify a number after -port"); + } + net_hostport = DEFAULTnet_hostport; + + net_numsockets = svs.maxclientslimit; + if (cls.state != ca_dedicated) + net_numsockets++; + if (COM_CheckParm("-listen") || cls.state == ca_dedicated) + listening = true; + + SetNetTime(); + + for (i = 0; i < net_numsockets; i++) + { + s = (qsocket_t *)Hunk_AllocName(sizeof(qsocket_t), "qsocket"); + s->next = net_freeSockets; + net_freeSockets = s; + s->disconnected = true; + } + + // allocate space for network message buffer + SZ_Alloc (&net_message, NET_MAXMESSAGE); + + Cvar_RegisterVariable (&net_messagetimeout); + Cvar_RegisterVariable (&hostname); + + Cmd_AddCommand ("slist", NET_Slist_f); + Cmd_AddCommand ("listen", NET_Listen_f); + Cmd_AddCommand ("maxplayers", MaxPlayers_f); + Cmd_AddCommand ("port", NET_Port_f); + + // initialize all the drivers + for (i = net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].Init() == -1) + continue; + i++; + net_drivers[net_driverlevel].initialized = true; + if (listening) + net_drivers[net_driverlevel].Listen (true); + } + + /* Loop_Init() returns -1 for dedicated server case, + * therefore the i == 0 check is correct */ + if (i == 0 + && cls.state == ca_dedicated + ) + { + Sys_Error("Network not available!"); + } + + if (*my_ipx_address) + { + Con_DPrintf("IPX address %s\n", my_ipx_address); + } + if (*my_tcpip_address) + { + Con_DPrintf("TCP/IP address %s\n", my_tcpip_address); + } +} + +/* +==================== +NET_Shutdown +==================== +*/ + +void NET_Shutdown (void) +{ + qsocket_t *sock; + + SetNetTime(); + + for (sock = net_activeSockets; sock; sock = sock->next) + NET_Close(sock); + +// +// shutdown the drivers +// + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == true) + { + net_drivers[net_driverlevel].Shutdown (); + net_drivers[net_driverlevel].initialized = false; + } + } +} + + +static PollProcedure *pollProcedureList = NULL; + +void NET_Poll(void) +{ + PollProcedure *pp; + + SetNetTime(); + + for (pp = pollProcedureList; pp; pp = pp->next) + { + if (pp->nextTime > net_time) + break; + pollProcedureList = pp->next; + pp->procedure(pp->arg); + } +} + + +void SchedulePollProcedure(PollProcedure *proc, double timeOffset) +{ + PollProcedure *pp, *prev; + + proc->nextTime = Sys_DoubleTime() + timeOffset; + for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next) + { + if (pp->nextTime >= proc->nextTime) + break; + prev = pp; + } + + if (prev == NULL) + { + proc->next = pollProcedureList; + pollProcedureList = proc; + return; + } + + proc->next = pp; + prev->next = proc; +} + diff --git a/source/net_sys.h b/source/net_sys.h new file mode 100644 index 0000000..77727e3 --- /dev/null +++ b/source/net_sys.h @@ -0,0 +1,199 @@ +/* + * net_sys.h -- common network system header. + * - depends on arch_def.h + * - may depend on q_stdinc.h + * + * Copyright (C) 2007-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NET_SYS_H__ +#define __NET_SYS_H__ + +#include +#include +#include +#include + +#if defined(PLATFORM_BSD) || defined(PLATFORM_OSX) || \ + defined(PLATFORM_AMIGA) /* bsdsocket.library */ || \ + defined(__GNU__) /* GNU/Hurd */ || defined(__riscos__) +/* struct sockaddr has unsigned char sa_len as the first member in BSD + * variants and the family member is also an unsigned char instead of an + * unsigned short. This should matter only when PLATFORM_UNIX is defined, + * however, checking for the offset of sa_family in every platform that + * provide a struct sockaddr doesn't hurt either (see down below for the + * compile time asserts.) */ +/* FIXME : GET RID OF THIS ABOMINATION !!! */ +#define HAVE_SA_LEN 1 +#define SA_FAM_OFFSET 1 +#else +#undef HAVE_SA_LEN +#define SA_FAM_OFFSET 0 +#endif /* BSD, sockaddr */ + +/* unix includes and compatibility macros */ +#if defined(PLATFORM_UNIX) || defined(PLATFORM_RISCOS) + +#include +#include +#if defined(__sun) || defined(sun) +#include +#include +#endif /* __sunos__ */ +#include +#include +#include +#include +#include + +typedef int sys_socket_t; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +#if defined(__APPLE__) && defined(SO_NKE) && !defined(SO_NOADDRERR) + /* ancient Mac OS X SDKs 10.2 and older are missing socklen_t */ +typedef int socklen_t; /* defining as signed int to match the old api */ +#endif /* ancient OSX SDKs */ + +#define SOCKETERRNO errno +#define ioctlsocket ioctl +#define closesocket close +#define selectsocket select +#define IOCTLARG_P(x) /* (char *) */ x + +#define NET_EWOULDBLOCK EWOULDBLOCK +#define NET_ECONNREFUSED ECONNREFUSED + +#define socketerror(x) strerror((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of unix stuff */ + + +/* amiga includes and compatibility macros */ +#if defined(PLATFORM_AMIGA) /* Amiga bsdsocket.library */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int sys_socket_t; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +#if !(defined(__AROS__) || defined(__amigaos4__)) +typedef LONG socklen_t; /* int32_t */ +#endif +#if !defined(__amigaos4__) +#if (LONG_MAX <= 2147483647L) +typedef unsigned long in_addr_t; /* u_int32_t */ +#else +typedef unsigned int in_addr_t; /* u_int32_t */ +#endif +#endif + +#define SOCKETERRNO Errno() +#define ioctlsocket IoctlSocket +#define closesocket CloseSocket +#define selectsocket(_N,_R,_W,_E,_T) \ + WaitSelect((_N),(_R),(_W),(_E),(_T),NULL) +#define IOCTLARG_P(x) (char *) x +#if defined(__amigaos4__) || defined(PLATFORM_AMIGAOS3) +#define inet_ntoa(x) Inet_NtoA(x.s_addr) /* Inet_NtoA(*(ULONG*)&x) */ +#define h_errno Errno() +#endif + +#define NET_EWOULDBLOCK EWOULDBLOCK +#define NET_ECONNREFUSED ECONNREFUSED + +#define socketerror(x) strerror((x)) +/* there is h_errno but no hstrerror() */ +#define hstrerror(x) strerror((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of amiga bsdsocket.library stuff */ + + +/* windows includes and compatibility macros */ +#if defined(PLATFORM_WINDOWS) + +/* NOTE: winsock[2].h already includes windows.h */ +#if !defined(_USE_WINSOCK2) +#include +#else +#include +#include +#endif + +/* there is no in_addr_t on windows: define it as + the type of the S_addr of in_addr structure */ +typedef u_long in_addr_t; /* uint32_t */ + +/* on windows, socklen_t is to be a winsock2 thing */ +#if !defined(IP_MSFILTER_SIZE) +typedef int socklen_t; +#endif /* socklen_t type */ + +typedef SOCKET sys_socket_t; + +#define selectsocket select +#define IOCTLARG_P(x) /* (u_long *) */ x + +#define SOCKETERRNO WSAGetLastError() +#define NET_EWOULDBLOCK WSAEWOULDBLOCK +#define NET_ECONNREFUSED WSAECONNREFUSED +/* must #include "wsaerror.h" for this : */ +#define socketerror(x) __WSAE_StrError((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of windows stuff */ + + +/* macros which may still be missing */ + +#if !defined(INADDR_NONE) +#define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif /* INADDR_NONE */ + +#if !defined(INADDR_LOOPBACK) +#define INADDR_LOOPBACK ((in_addr_t) 0x7f000001) /* 127.0.0.1 */ +#endif /* INADDR_LOOPBACK */ + + +#if !defined(MAXHOSTNAMELEN) +/* SUSv2 guarantees that `Host names are limited to 255 bytes'. + POSIX 1003.1-2001 guarantees that `Host names (not including + the terminating NUL) are limited to HOST_NAME_MAX bytes'. */ +#define MAXHOSTNAMELEN 256 +#endif /* MAXHOSTNAMELEN */ + + +#endif /* __NET_SYS_H__ */ + diff --git a/source/net_udp.c b/source/net_udp.c new file mode 100644 index 0000000..b545851 --- /dev/null +++ b/source/net_udp.c @@ -0,0 +1,462 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static sys_socket_t net_broadcastsocket = 0; +static struct sockaddr_in broadcastaddr; + +static in_addr_t myAddr; + +#include "net_udp.h" + +//============================================================================= + +sys_socket_t UDP_Init (void) +{ + int err; + char *tst; + char buff[MAXHOSTNAMELEN]; + struct hostent *local; + struct qsockaddr addr; + + if (COM_CheckParm ("-noudp")) + return INVALID_SOCKET; + + // determine my name & address + myAddr = htonl(INADDR_LOOPBACK); + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("UDP_Init: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; +#ifdef PLATFORM_OSX + // ericw -- if our hostname ends in ".local" (a macOS thing), + // don't bother calling gethostbyname(), because it blocks for a few seconds + // and then fails (on my system anyway.) + tst = strstr(buff, ".local"); + if (tst && tst[6] == '\0') + { + Con_SafePrintf("UDP_Init: skipping gethostbyname for %s\n", buff); + } + else +#endif + if (!(local = gethostbyname(buff))) + { + Con_SafePrintf("UDP_Init: gethostbyname failed (%s)\n", + hstrerror(h_errno)); + } + else if (local->h_addrtype != AF_INET) + { + Con_SafePrintf("UDP_Init: address from gethostbyname not IPv4\n"); + } + else + { + myAddr = *(in_addr_t *)local->h_addr_list[0]; + } + } + + if ((net_controlsocket = UDP_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("UDP_Init: Unable to open control socket, UDP disabled\n"); + return INVALID_SOCKET; + } + + broadcastaddr.sin_family = AF_INET; + broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddr.sin_port = htons((unsigned short)net_hostport); + + UDP_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, UDP_AddrToString (&addr)); + tst = strrchr(my_tcpip_address, ':'); + if (tst) *tst = 0; + + Con_SafePrintf("UDP Initialized\n"); + tcpipAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void UDP_Shutdown (void) +{ + UDP_Listen (false); + UDP_CloseSocket (net_controlsocket); +} + +//============================================================================= + +void UDP_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + if ((net_acceptsocket = UDP_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("UDP_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + UDP_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t UDP_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in address; + int _true = 1; + int err; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(struct sockaddr_in)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + return newsocket; + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + UDP_CloseSocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int UDP_CloseSocket (sys_socket_t socketid) +{ + if (socketid == net_broadcastsocket) + net_broadcastsocket = 0; + return closesocket (socketid); +} + +//============================================================================= + +/* +============ +PartialIPAddress + +this lets you type only as much of the net address as required, using +the local network components to fill in the rest +============ +*/ +static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) +{ + char buff[256]; + char *b; + int addr, mask, num, port, run; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') + b++; + + addr = 0; + mask = -1; + while (*b == '.') + { + b++; + num = 0; + run = 0; + while (!( *b < '0' || *b > '9')) + { + num = num*10 + *b++ - '0'; + if (++run > 3) + return -1; + } + if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0) + return -1; + if (num < 0 || num > 255) + return -1; + mask <<= 8; + addr = (addr<<8) + num; + } + + if (*b++ == ':') + port = atoi(b); + else + port = net_hostport; + + hostaddr->qsa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = + (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} + +//============================================================================= + +int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t UDP_CheckNewConnections (void) +{ + int available; + struct sockaddr_in from; + socklen_t fromlen; + char buff[1]; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (ioctl (net_acceptsocket, FIONREAD, &available) == -1) + { + int err = SOCKETERRNO; + Sys_Error ("UDP: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); + } + if (available) + return net_acceptsocket; + // quietly absorb empty packets + recvfrom (net_acceptsocket, buff, 0, 0, (struct sockaddr *) &from, &fromlen); + return INVALID_SOCKET; +} + +//============================================================================= + +int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + int ret; + + ret = recvfrom (socketid, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("UDP_Read, recvfrom: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +static int UDP_MakeSocketBroadcastCapable (sys_socket_t socketid) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socketid, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) + == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); + return -1; + } + net_broadcastsocket = socketid; + + return 0; +} + +//============================================================================= + +int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + int ret; + + if (socketid != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + Sys_Error("Attempted to use multiple broadcasts sockets"); + ret = UDP_MakeSocketBroadcastCapable (socketid); + if (ret == -1) + { + Con_Printf("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return UDP_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + int ret; + + ret = sendto (socketid, buf, len, 0, (struct sockaddr *)addr, + sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("UDP_Write, sendto: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +const char *UDP_AddrToString (struct qsockaddr *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + q_snprintf (buffer, sizeof(buffer), "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} + +//============================================================================= + +int UDP_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int ha1, ha2, ha3, ha4, hp, ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)hp); + return 0; +} + +//============================================================================= + +int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + in_addr_t a; + + memset(addr, 0, sizeof(struct qsockaddr)); + if (getsockname(socketid, (struct sockaddr *)addr, &addrlen) != 0) + return -1; + + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} + +//============================================================================= + +int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, UDP_AddrToString (addr)); + return 0; +} + +//============================================================================= + +int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = + *(in_addr_t *)hostentry->h_addr_list[0]; + + return 0; +} + +//============================================================================= + +int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} + +//============================================================================= + +int UDP_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} + + +int UDP_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/source/net_udp.h b/source/net_udp.h new file mode 100644 index 0000000..5df71df --- /dev/null +++ b/source/net_udp.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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_udp_h +#define __net_udp_h + +sys_socket_t UDP_Init (void); +void UDP_Shutdown (void); +void UDP_Listen (qboolean state); +sys_socket_t UDP_OpenSocket (int port); +int UDP_CloseSocket (sys_socket_t socketid); +int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t UDP_CheckNewConnections (void); +int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *UDP_AddrToString (struct qsockaddr *addr); +int UDP_StringToAddr (const char *string, struct qsockaddr *addr); +int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name); +int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr); +int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int UDP_GetSocketPort (struct qsockaddr *addr); +int UDP_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __net_udp_h */ + diff --git a/source/net_win.c b/source/net_win.c new file mode 100644 index 0000000..2d4ec9e --- /dev/null +++ b/source/net_win.c @@ -0,0 +1,122 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +#include "net_dgrm.h" +#include "net_loop.h" + +net_driver_t net_drivers[] = +{ + { "Loopback", + false, + Loop_Init, + Loop_Listen, + Loop_SearchForHosts, + Loop_Connect, + Loop_CheckNewConnections, + Loop_GetMessage, + Loop_SendMessage, + Loop_SendUnreliableMessage, + Loop_CanSendMessage, + Loop_CanSendUnreliableMessage, + Loop_Close, + Loop_Shutdown + }, + + { "Datagram", + false, + Datagram_Init, + Datagram_Listen, + Datagram_SearchForHosts, + Datagram_Connect, + Datagram_CheckNewConnections, + Datagram_GetMessage, + Datagram_SendMessage, + Datagram_SendUnreliableMessage, + Datagram_CanSendMessage, + Datagram_CanSendUnreliableMessage, + Datagram_Close, + Datagram_Shutdown + } +}; + +const int net_numdrivers = (sizeof(net_drivers) / sizeof(net_drivers[0])); + + +#include "net_wins.h" +#include "net_wipx.h" + +net_landriver_t net_landrivers[] = +{ + { "Winsock TCPIP", + false, + 0, + WINS_Init, + WINS_Shutdown, + WINS_Listen, + WINS_OpenSocket, + WINS_CloseSocket, + WINS_Connect, + WINS_CheckNewConnections, + WINS_Read, + WINS_Write, + WINS_Broadcast, + WINS_AddrToString, + WINS_StringToAddr, + WINS_GetSocketAddr, + WINS_GetNameFromAddr, + WINS_GetAddrFromName, + WINS_AddrCompare, + WINS_GetSocketPort, + WINS_SetSocketPort + }, + + { "Winsock IPX", + false, + 0, + WIPX_Init, + WIPX_Shutdown, + WIPX_Listen, + WIPX_OpenSocket, + WIPX_CloseSocket, + WIPX_Connect, + WIPX_CheckNewConnections, + WIPX_Read, + WIPX_Write, + WIPX_Broadcast, + WIPX_AddrToString, + WIPX_StringToAddr, + WIPX_GetSocketAddr, + WIPX_GetNameFromAddr, + WIPX_GetAddrFromName, + WIPX_AddrCompare, + WIPX_GetSocketPort, + WIPX_SetSocketPort + } +}; + +const int net_numlandrivers = (sizeof(net_landrivers) / sizeof(net_landrivers[0])); + diff --git a/source/net_wins.c b/source/net_wins.c new file mode 100644 index 0000000..f45a522 --- /dev/null +++ b/source/net_wins.c @@ -0,0 +1,544 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static sys_socket_t net_broadcastsocket = 0; +static struct sockaddr_in broadcastaddr; + +static in_addr_t myAddr; + +#include "net_wins.h" + +int winsock_initialized = 0; +WSADATA winsockdata; +#define __wsaerr_static /* not static: used by net_wipx.c too */ +#include "wsaerror.h" + +//============================================================================= + +#if !defined(_USE_WINSOCK2) +static double blocktime; + +static INT_PTR PASCAL FAR BlockingHook (void) +{ + MSG msg; + BOOL ret; + + if ((Sys_DoubleTime() - blocktime) > 2.0) + { + WSACancelBlockingCall(); + return FALSE; + } + + /* get the next message, if any */ + ret = (BOOL) PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + + /* if we got one, process it */ + if (ret) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* TRUE if we got a message */ + return ret; +} +#endif /* ! _USE_WINSOCK2 */ + + +static void WINS_GetLocalAddress (void) +{ + struct hostent *local = NULL; + char buff[MAXHOSTNAMELEN]; + in_addr_t addr; + int err; + + if (myAddr != INADDR_ANY) + return; + + if (gethostname(buff, MAXHOSTNAMELEN) == SOCKET_ERROR) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_GetLocalAddress: gethostname failed (%s)\n", + socketerror(err)); + return; + } + + buff[MAXHOSTNAMELEN - 1] = 0; +#ifndef _USE_WINSOCK2 + blocktime = Sys_DoubleTime(); + WSASetBlockingHook(BlockingHook); +#endif + local = gethostbyname(buff); + err = WSAGetLastError(); +#ifndef _USE_WINSOCK2 + WSAUnhookBlockingHook(); +#endif + if (local == NULL) + { + Con_SafePrintf("WINS_GetLocalAddress: gethostbyname failed (%s)\n", + __WSAE_StrError(err)); + return; + } + + myAddr = *(in_addr_t *)local->h_addr_list[0]; + + addr = ntohl(myAddr); + sprintf(my_tcpip_address, "%ld.%ld.%ld.%ld", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); +} + + +sys_socket_t WINS_Init (void) +{ + int i, err; + char buff[MAXHOSTNAMELEN]; + + if (COM_CheckParm ("-noudp")) + return -1; + + if (winsock_initialized == 0) + { + err = WSAStartup(MAKEWORD(1,1), &winsockdata); + if (err != 0) + { + Con_SafePrintf("Winsock initialization failed (%s)\n", + socketerror(err)); + return INVALID_SOCKET; + } + } + winsock_initialized++; + + // determine my name & address + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_Init: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; + } + i = COM_CheckParm ("-ip"); + if (i) + { + if (i < com_argc-1) + { + myAddr = inet_addr(com_argv[i+1]); + if (myAddr == INADDR_NONE) + Sys_Error ("%s is not a valid IP address", com_argv[i+1]); + strcpy(my_tcpip_address, com_argv[i+1]); + } + else + { + Sys_Error ("NET_Init: you must specify an IP address after -ip"); + } + } + else + { + myAddr = INADDR_ANY; + strcpy(my_tcpip_address, "INADDR_ANY"); + } + + if ((net_controlsocket = WINS_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("WINS_Init: Unable to open control socket, UDP disabled\n"); + if (--winsock_initialized == 0) + WSACleanup (); + return INVALID_SOCKET; + } + + broadcastaddr.sin_family = AF_INET; + broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddr.sin_port = htons((unsigned short)net_hostport); + + Con_SafePrintf("UDP Initialized\n"); + tcpipAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void WINS_Shutdown (void) +{ + WINS_Listen (false); + WINS_CloseSocket (net_controlsocket); + if (--winsock_initialized == 0) + WSACleanup (); +} + +//============================================================================= + +void WINS_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + WINS_GetLocalAddress(); + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("WINS_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t WINS_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in address; + u_long _true = 1; + int err; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(struct sockaddr_in)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = myAddr; + address.sin_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + return newsocket; + + if (tcpipAvailable) + { + err = SOCKETERRNO; + Sys_Error ("Unable to bind to %s (%s)", + WINS_AddrToString ((struct qsockaddr *) &address), + socketerror(err)); + return INVALID_SOCKET; /* not reached */ + } + /* else: we are still in init phase, no need to error */ + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + closesocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int WINS_CloseSocket (sys_socket_t socketid) +{ + if (socketid == net_broadcastsocket) + net_broadcastsocket = 0; + return closesocket (socketid); +} + +//============================================================================= + +/* +============ +PartialIPAddress + +this lets you type only as much of the net address as required, using +the local network components to fill in the rest +============ +*/ +static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) +{ + char buff[256]; + char *b; + int addr, mask, num, port, run; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') + b++; + + addr = 0; + mask = -1; + while (*b == '.') + { + b++; + num = 0; + run = 0; + while (!( *b < '0' || *b > '9')) + { + num = num*10 + *b++ - '0'; + if (++run > 3) + return -1; + } + if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0) + return -1; + if (num < 0 || num > 255) + return -1; + mask <<= 8; + addr = (addr<<8) + num; + } + + if (*b++ == ':') + port = Q_atoi(b); + else + port = net_hostport; + + hostaddr->qsa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = + (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} + +//============================================================================= + +int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t WINS_CheckNewConnections (void) +{ + char buf[4096]; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (recvfrom (net_acceptsocket, buf, sizeof(buf), MSG_PEEK, NULL, NULL) + != SOCKET_ERROR) + { + return net_acceptsocket; + } + return INVALID_SOCKET; +} + +//============================================================================= + +int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + int ret; + + ret = recvfrom (socketid, (char *)buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("WINS_Read, recvfrom: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +static int WINS_MakeSocketBroadcastCapable (sys_socket_t socketid) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socketid, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) + == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); + return -1; + } + net_broadcastsocket = socketid; + + return 0; +} + +//============================================================================= + +int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + int ret; + + if (socketid != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + Sys_Error("Attempted to use multiple broadcasts sockets"); + WINS_GetLocalAddress(); + ret = WINS_MakeSocketBroadcastCapable (socketid); + if (ret == -1) + { + Con_Printf("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + int ret; + + ret = sendto (socketid, (char *)buf, len, 0, (struct sockaddr *)addr, + sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("WINS_Write, sendto: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +const char *WINS_AddrToString (struct qsockaddr *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} + +//============================================================================= + +int WINS_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int ha1, ha2, ha3, ha4, hp, ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)hp); + return 0; +} + +//============================================================================= + +int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + in_addr_t a; + + memset(addr, 0, sizeof(struct qsockaddr)); + getsockname(socketid, (struct sockaddr *)addr, &addrlen); + + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} + +//============================================================================= + +int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hostentry) + { + Q_strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + Q_strcpy (name, WINS_AddrToString (addr)); + return 0; +} + +//============================================================================= + +int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = + *(in_addr_t *)hostentry->h_addr_list[0]; + + return 0; +} + +//============================================================================= + +int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} + +//============================================================================= + +int WINS_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} + + +int WINS_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/source/net_wins.h b/source/net_wins.h new file mode 100644 index 0000000..59abda4 --- /dev/null +++ b/source/net_wins.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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_WINSOCK_H +#define __NET_WINSOCK_H + +sys_socket_t WINS_Init (void); +void WINS_Shutdown (void); +void WINS_Listen (qboolean state); +sys_socket_t WINS_OpenSocket (int port); +int WINS_CloseSocket (sys_socket_t socketid); +int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t WINS_CheckNewConnections (void); +int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *WINS_AddrToString (struct qsockaddr *addr); +int WINS_StringToAddr (const char *string, struct qsockaddr *addr); +int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr); +int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int WINS_GetSocketPort (struct qsockaddr *addr); +int WINS_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __NET_WINSOCK_H */ + diff --git a/source/net_wipx.c b/source/net_wipx.c new file mode 100644 index 0000000..155686b --- /dev/null +++ b/source/net_wipx.c @@ -0,0 +1,448 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ + +// net_wipx.c + +#include "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include +#include "quakedef.h" +#include "net_defs.h" +#include "net_wipx.h" + +extern cvar_t hostname; + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static struct sockaddr_ipx broadcastaddr; + +/* externs from net_wins.c: */ +extern qboolean winsock_initialized; +extern WSADATA winsockdata; +extern const char *__WSAE_StrError (int); + +#define IPXSOCKETS 18 +static sys_socket_t ipxsocket[IPXSOCKETS]; +static int sequence[IPXSOCKETS]; + +//============================================================================= + +sys_socket_t WIPX_Init (void) +{ + int i, err; + char *colon; + char buff[MAXHOSTNAMELEN]; + struct qsockaddr addr; + + if (COM_CheckParm ("-noipx")) + return INVALID_SOCKET; + + if (winsock_initialized == 0) + { + err = WSAStartup(MAKEWORD(1,1), &winsockdata); + if (err != 0) + { + Con_SafePrintf("Winsock initialization failed (%s)\n", + socketerror(err)); + return INVALID_SOCKET; + } + } + winsock_initialized++; + + for (i = 0; i < IPXSOCKETS; i++) + ipxsocket[i] = 0; + + // determine my name & address + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("WIPX_Init: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; + } + + if ((net_controlsocket = WIPX_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("WIPX_Init: Unable to open control socket, IPX disabled\n"); + if (--winsock_initialized == 0) + WSACleanup (); + return INVALID_SOCKET; + } + + broadcastaddr.sa_family = AF_IPX; + memset(broadcastaddr.sa_netnum, 0, 4); + memset(broadcastaddr.sa_nodenum, 0xff, 6); + broadcastaddr.sa_socket = htons((unsigned short)net_hostport); + + WIPX_GetSocketAddr (net_controlsocket, &addr); + Q_strcpy(my_ipx_address, WIPX_AddrToString (&addr)); + colon = Q_strrchr (my_ipx_address, ':'); + if (colon) + *colon = 0; + + Con_SafePrintf("IPX Initialized\n"); + ipxAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void WIPX_Shutdown (void) +{ + WIPX_Listen (false); + WIPX_CloseSocket (net_controlsocket); + if (--winsock_initialized == 0) + WSACleanup (); +} + +//============================================================================= + +void WIPX_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + if ((net_acceptsocket = WIPX_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("WIPX_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + WIPX_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t WIPX_OpenSocket (int port) +{ + int err; + sys_socket_t handle, newsocket; + struct sockaddr_ipx address; + u_long _true = 1; + + for (handle = 0; handle < IPXSOCKETS; handle++) + { + if (ipxsocket[handle] == 0) + break; + } + if (handle == IPXSOCKETS) + { + Con_SafePrintf("WIPX_OpenSocket: Out of free IPX handles.\n"); + return INVALID_SOCKET; + } + + if ((newsocket = socket (AF_IPX, SOCK_DGRAM, NSPROTO_IPX)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("WIPX_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) + == SOCKET_ERROR) + goto ErrorReturn; + + address.sa_family = AF_IPX; + memset(address.sa_netnum, 0, 4); + memset(address.sa_nodenum, 0, 6);; + address.sa_socket = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + { + ipxsocket[handle] = newsocket; + sequence[handle] = 0; + return handle; + } + + if (ipxAvailable) + { + err = SOCKETERRNO; + Sys_Error ("IPX bind failed (%s)", socketerror(err)); + return INVALID_SOCKET; /* not reached */ + } + /* else: we are still in init phase, no need to error */ + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("WIPX_OpenSocket: %s\n", socketerror(err)); + closesocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int WIPX_CloseSocket (sys_socket_t handle) +{ + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + ret = closesocket (socketid); + ipxsocket[handle] = 0; + return ret; +} + +//============================================================================= + +int WIPX_Connect (sys_socket_t handle, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t WIPX_CheckNewConnections (void) +{ + u_long available; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (ioctlsocket (ipxsocket[net_acceptsocket], FIONREAD, &available) == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Sys_Error ("WIPX: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); + } + if (available) + return net_acceptsocket; + return INVALID_SOCKET; +} + +//============================================================================= + +static byte netpacketBuffer[NET_DATAGRAMSIZE + 4]; + +int WIPX_Read (sys_socket_t handle, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + ret = recvfrom (socketid, (char *)netpacketBuffer, len+4, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("WIPX_Read, recvfrom: %s\n", socketerror(err)); + } + + if (ret < 4) + return 0; + + // remove sequence number, it's only needed for DOS IPX + ret -= 4; + memcpy(buf, netpacketBuffer+4, ret); + + return ret; +} + +//============================================================================= + +int WIPX_Broadcast (sys_socket_t handle, byte *buf, int len) +{ + return WIPX_Write (handle, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int WIPX_Write (sys_socket_t handle, byte *buf, int len, struct qsockaddr *addr) +{ + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + // build packet with sequence number + memcpy(&netpacketBuffer[0], &sequence[handle], 4); + sequence[handle]++; + memcpy(&netpacketBuffer[4], buf, len); + len += 4; + + ret = sendto (socketid, (char *)netpacketBuffer, len, 0, (struct sockaddr *)addr, sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("WIPX_Write, sendto: %s\n", socketerror(err)); + } + + return ret; +} + +//============================================================================= + +const char *WIPX_AddrToString (struct qsockaddr *addr) +{ + static char buf[28]; + + sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", + ((struct sockaddr_ipx *)addr)->sa_netnum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[4] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[5] & 0xff, + ntohs(((struct sockaddr_ipx *)addr)->sa_socket) + ); + return buf; +} + +//============================================================================= + +int WIPX_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int val; + char buf[3]; + + buf[2] = 0; + Q_memset(addr, 0, sizeof(struct qsockaddr)); + addr->qsa_family = AF_IPX; + +#define DO(src,dest) do { \ + buf[0] = string[src]; \ + buf[1] = string[src + 1]; \ + if (sscanf (buf, "%x", &val) != 1) \ + return -1; \ + ((struct sockaddr_ipx *)addr)->dest = val; \ + } while (0) + + DO(0, sa_netnum[0]); + DO(2, sa_netnum[1]); + DO(4, sa_netnum[2]); + DO(6, sa_netnum[3]); + DO(9, sa_nodenum[0]); + DO(11, sa_nodenum[1]); + DO(13, sa_nodenum[2]); + DO(15, sa_nodenum[3]); + DO(17, sa_nodenum[4]); + DO(19, sa_nodenum[5]); +#undef DO + + sscanf (&string[22], "%u", &val); + ((struct sockaddr_ipx *)addr)->sa_socket = htons((unsigned short)val); + + return 0; +} + +//============================================================================= + +int WIPX_GetSocketAddr (sys_socket_t handle, struct qsockaddr *addr) +{ + sys_socket_t socketid = ipxsocket[handle]; + socklen_t addrlen = sizeof(struct qsockaddr); + + Q_memset(addr, 0, sizeof(struct qsockaddr)); + if (getsockname(socketid, (struct sockaddr *)addr, &addrlen) != 0) + { + int err = SOCKETERRNO; + /* FIXME: what action should be taken?... */ + Con_SafePrintf ("WIPX, getsockname: %s\n", socketerror(err)); + } + + return 0; +} + +//============================================================================= + +int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + Q_strcpy(name, WIPX_AddrToString(addr)); + return 0; +} + +//============================================================================= + +int WIPX_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + int n; + char buf[32]; + + n = Q_strlen(name); + + if (n == 12) + { + sprintf(buf, "00000000:%s:%u", name, net_hostport); + return WIPX_StringToAddr (buf, addr); + } + if (n == 21) + { + sprintf(buf, "%s:%u", name, net_hostport); + return WIPX_StringToAddr (buf, addr); + } + if (n > 21 && n <= 27) + return WIPX_StringToAddr (name, addr); + + return -1; +} + +//============================================================================= + +int WIPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (*((struct sockaddr_ipx *)addr1)->sa_netnum && *((struct sockaddr_ipx *)addr2)->sa_netnum) + { + if (memcmp(((struct sockaddr_ipx *)addr1)->sa_netnum, ((struct sockaddr_ipx *)addr2)->sa_netnum, 4) != 0) + return -1; + } + + if (memcmp(((struct sockaddr_ipx *)addr1)->sa_nodenum, ((struct sockaddr_ipx *)addr2)->sa_nodenum, 6) != 0) + return -1; + + if (((struct sockaddr_ipx *)addr1)->sa_socket != ((struct sockaddr_ipx *)addr2)->sa_socket) + return 1; + + return 0; +} + +//============================================================================= + +int WIPX_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_ipx *)addr)->sa_socket); +} + + +int WIPX_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_ipx *)addr)->sa_socket = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/source/net_wipx.h b/source/net_wipx.h new file mode 100644 index 0000000..e8a24d3 --- /dev/null +++ b/source/net_wipx.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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_WINIPX_H +#define __NET_WINIPX_H + +sys_socket_t WIPX_Init (void); +void WIPX_Shutdown (void); +void WIPX_Listen (qboolean state); +sys_socket_t WIPX_OpenSocket (int port); +int WIPX_CloseSocket (sys_socket_t socketid); +int WIPX_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t WIPX_CheckNewConnections (void); +int WIPX_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WIPX_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WIPX_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *WIPX_AddrToString (struct qsockaddr *addr); +int WIPX_StringToAddr (const char *string, struct qsockaddr *addr); +int WIPX_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WIPX_GetAddrFromName (const char *name, struct qsockaddr *addr); +int WIPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int WIPX_GetSocketPort (struct qsockaddr *addr); +int WIPX_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __NET_WINIPX_H */ + diff --git a/source/pl_linux.c b/source/pl_linux.c new file mode 100644 index 0000000..c30a66e --- /dev/null +++ b/source/pl_linux.c @@ -0,0 +1,80 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 + +static const Uint8 bmp_bytes[] = +{ +#include "qs_bmp.h" +}; + +void PL_SetWindowIcon (void) +{ + SDL_RWops *rwop; + SDL_Surface *icon; + Uint32 colorkey; + + /* SDL_RWFromConstMem() requires SDL >= 1.2.7 */ + rwop = SDL_RWFromConstMem(bmp_bytes, sizeof(bmp_bytes)); + if (rwop == NULL) + return; + icon = SDL_LoadBMP_RW(rwop, 1); + if (icon == NULL) + return; + /* make pure magenta (#ff00ff) tranparent */ + colorkey = SDL_MapRGB(icon->format, 255, 0, 255); + SDL_SetColorKey(icon, SDL_TRUE, colorkey); + SDL_SetWindowIcon((SDL_Window*) VID_GetWindow(), icon); + SDL_FreeSurface(icon); +} + +void PL_VID_Shutdown (void) +{ +} + +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; + char *cliptext = SDL_GetClipboardText(); + + if (cliptext != NULL) + { + size_t size = strlen(cliptext) + 1; + /* this is intended for simple small text copies + * such as an ip address, etc: do chop the size + * here, otherwise we may experience Z_Malloc() + * failures and all other not-oh-so-fun stuff. */ + size = q_min(MAX_CLIPBOARDTXT, size); + data = (char *) Z_Malloc(size); + q_strlcpy (data, cliptext, size); + } + + return data; +} + +void PL_ErrorDialog (const char *errorMsg) +{ +} + diff --git a/source/pl_osx.m b/source/pl_osx.m new file mode 100644 index 0000000..88aad09 --- /dev/null +++ b/source/pl_osx.m @@ -0,0 +1,69 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 +#import + +void PL_SetWindowIcon (void) +{ +/* nothing to do on OS X */ +} + +void PL_VID_Shutdown (void) +{ +} + +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSArray* types = [pasteboard types]; + + if ([types containsObject: NSStringPboardType]) { + NSString* clipboardString = [pasteboard stringForType: NSStringPboardType]; + if (clipboardString != NULL && [clipboardString length] > 0) { + size_t sz = [clipboardString length] + 1; + sz = q_min(MAX_CLIPBOARDTXT, sz); + data = (char *) Z_Malloc(sz); +#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* for ppc builds targeting 10.3 and older */ + q_strlcpy (data, [clipboardString cString], sz); +#else + q_strlcpy (data, [clipboardString cStringUsingEncoding: NSASCIIStringEncoding], sz); +#endif + } + } + return data; +} + +void PL_ErrorDialog(const char *errorMsg) +{ +#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* ppc builds targeting 10.3 and older */ + NSString* msg = [NSString stringWithCString:errorMsg]; +#else + NSString* msg = [NSString stringWithCString:errorMsg encoding:NSASCIIStringEncoding]; +#endif + NSRunCriticalAlertPanel (@"Quake Error", @"%@", @"OK", nil, nil, msg); +} + diff --git a/source/pl_win.c b/source/pl_win.c new file mode 100644 index 0000000..0241743 --- /dev/null +++ b/source/pl_win.c @@ -0,0 +1,97 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 + +static HICON icon; + +void PL_SetWindowIcon (void) +{ + HINSTANCE handle; + SDL_SysWMinfo wminfo; + HWND hwnd; + + handle = GetModuleHandle(NULL); + icon = LoadIcon(handle, "icon"); + + if (!icon) + return; /* no icon in the exe */ + + SDL_VERSION(&wminfo.version); + + if (SDL_GetWindowWMInfo((SDL_Window*) VID_GetWindow(), &wminfo) != SDL_TRUE) + return; /* wrong SDL version */ + + hwnd = wminfo.info.win.window; +#ifdef _WIN64 + SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR) icon); +#else + SetClassLong(hwnd, GCL_HICON, (LONG) icon); +#endif +} + +void PL_VID_Shutdown (void) +{ + DestroyIcon(icon); +} + +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; + char *cliptext; + + if (OpenClipboard(NULL) != 0) + { + HANDLE hClipboardData; + + if ((hClipboardData = GetClipboardData(CF_TEXT)) != NULL) + { + cliptext = (char *) GlobalLock(hClipboardData); + if (cliptext != NULL) + { + size_t size = GlobalSize(hClipboardData) + 1; + /* this is intended for simple small text copies + * such as an ip address, etc: do chop the size + * here, otherwise we may experience Z_Malloc() + * failures and all other not-oh-so-fun stuff. */ + size = q_min(MAX_CLIPBOARDTXT, size); + data = (char *) Z_Malloc(size); + q_strlcpy (data, cliptext, size); + GlobalUnlock (hClipboardData); + } + } + CloseClipboard (); + } + return data; +} + +void PL_ErrorDialog(const char *errorMsg) +{ + MessageBox (NULL, errorMsg, "Quake Error", + MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); +} + diff --git a/source/platform.h b/source/platform.h new file mode 100644 index 0000000..679ecff --- /dev/null +++ b/source/platform.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_PLATFORM_H +#define _QUAKE_PLATFORM_H + +/* platform dependent way to set the window icon */ +void PL_SetWindowIcon(void); + +/* platform dependent cleanup */ +void PL_VID_Shutdown (void); + +/* retrieve text from the clipboard (returns Z_Malloc()'ed data) */ +char *PL_GetClipboardData (void); + +/* show an error dialog */ +void PL_ErrorDialog(const char *text); + +#endif /* _QUAKE_PLATFORM_H */ + diff --git a/source/pr_cmds.c b/source/pr_cmds.c new file mode 100644 index 0000000..ad3a203 --- /dev/null +++ b/source/pr_cmds.c @@ -0,0 +1,1765 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +#define STRINGTEMP_BUFFERS 16 +#define STRINGTEMP_LENGTH 1024 +static char pr_string_temp[STRINGTEMP_BUFFERS][STRINGTEMP_LENGTH]; +static byte pr_string_tempindex = 0; + +static char *PR_GetTempString (void) +{ + return pr_string_temp[(STRINGTEMP_BUFFERS-1) & ++pr_string_tempindex]; +} + +#define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) + +#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 + +/* +=============================================================================== + + BUILT-IN FUNCTIONS + +=============================================================================== +*/ + +static char *PF_VarString (int first) +{ + int i; + static char out[1024]; + size_t s; + + out[0] = 0; + s = 0; + for (i = first; i < pr_argc; i++) + { + s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), sizeof(out)); + if (s >= sizeof(out)) + { + Con_Warning("PF_VarString: overflow (string truncated)\n"); + return out; + } + } + if (s > 255) + { + if (!dev_overflows.varstring || dev_overflows.varstring + CONSOLE_RESPAM_TIME < realtime) + { + Con_DWarning("PF_VarString: %i characters exceeds standard limit of 255 (max = %d).\n", (int) s, (int)(sizeof(out) - 1)); + dev_overflows.varstring = realtime; + } + } + return out; +} + + +/* +================= +PF_error + +This is a TERMINAL error, which will kill off the entire server. +Dumps self. + +error(value) +================= +*/ +static void PF_error (void) +{ + char *s; + edict_t *ed; + + s = PF_VarString(0); + Con_Printf ("======SERVER ERROR in %s:\n%s\n", + PR_GetString(pr_xfunction->s_name), s); + ed = PROG_TO_EDICT(pr_global_struct->self); + ED_Print (ed); + + Host_Error ("Program error"); +} + +/* +================= +PF_objerror + +Dumps out self, then an error message. The program is aborted and self is +removed, but the level can continue. + +objerror(value) +================= +*/ +static void PF_objerror (void) +{ + char *s; + edict_t *ed; + + s = PF_VarString(0); + Con_Printf ("======OBJECT ERROR in %s:\n%s\n", + PR_GetString(pr_xfunction->s_name), s); + ed = PROG_TO_EDICT(pr_global_struct->self); + ED_Print (ed); + ED_Free (ed); + + //Host_Error ("Program error"); //johnfitz -- by design, this should not be fatal +} + + + +/* +============== +PF_makevectors + +Writes new values for v_forward, v_up, and v_right based on angles +makevectors(vector) +============== +*/ +static void PF_makevectors (void) +{ + AngleVectors (G_VECTOR(OFS_PARM0), pr_global_struct->v_forward, pr_global_struct->v_right, pr_global_struct->v_up); +} + +/* +================= +PF_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 PF_setorigin (void) +{ + edict_t *e; + float *org; + + e = G_EDICT(OFS_PARM0); + org = G_VECTOR(OFS_PARM1); + VectorCopy (org, e->v.origin); + SV_LinkEdict (e, false); +} + + +static void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate) +{ + float *angles; + vec3_t rmin, rmax; + float bounds[2][3]; + float xvector[2], yvector[2]; + float a; + vec3_t base, transformed; + int i, j, k, l; + + for (i = 0; i < 3; i++) + if (minvec[i] > maxvec[i]) + PR_RunError ("backwards mins/maxs"); + + rotate = false; // FIXME: implement rotation properly again + + if (!rotate) + { + VectorCopy (minvec, rmin); + VectorCopy (maxvec, rmax); + } + else + { + // find min / max for rotations + angles = e->v.angles; + + a = angles[1]/180 * M_PI; + + xvector[0] = cos(a); + xvector[1] = sin(a); + yvector[0] = -sin(a); + yvector[1] = cos(a); + + VectorCopy (minvec, bounds[0]); + VectorCopy (maxvec, bounds[1]); + + rmin[0] = rmin[1] = rmin[2] = 9999; + rmax[0] = rmax[1] = rmax[2] = -9999; + + for (i = 0; i <= 1; i++) + { + base[0] = bounds[i][0]; + for (j = 0; j <= 1; j++) + { + base[1] = bounds[j][1]; + for (k = 0; k <= 1; k++) + { + base[2] = bounds[k][2]; + + // transform the point + transformed[0] = xvector[0]*base[0] + yvector[0]*base[1]; + transformed[1] = xvector[1]*base[0] + yvector[1]*base[1]; + transformed[2] = base[2]; + + for (l = 0; l < 3; l++) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + } + +// set derived values + VectorCopy (rmin, e->v.mins); + VectorCopy (rmax, e->v.maxs); + VectorSubtract (maxvec, minvec, e->v.size); + + SV_LinkEdict (e, false); +} + +/* +================= +PF_setsize + +the size box is rotated by the current angle + +setsize (entity, minvector, maxvector) +================= +*/ +static void PF_setsize (void) +{ + edict_t *e; + float *minvec, *maxvec; + + e = G_EDICT(OFS_PARM0); + minvec = G_VECTOR(OFS_PARM1); + maxvec = G_VECTOR(OFS_PARM2); + SetMinMaxSize (e, minvec, maxvec, false); +} + + +/* +================= +PF_setmodel + +setmodel(entity, model) +================= +*/ +static void PF_setmodel (void) +{ + int i; + const char *m, **check; + qmodel_t *mod; + edict_t *e; + + e = G_EDICT(OFS_PARM0); + m = G_STRING(OFS_PARM1); + +// check to see if model was properly precached + for (i = 0, check = sv.model_precache; *check; i++, check++) + { + if (!strcmp(*check, m)) + break; + } + + if (!*check) + { + PR_RunError ("no precache: %s", m); + } + e->v.model = PR_SetEngineString(*check); + e->v.modelindex = i; //SV_ModelIndex (m); + + mod = sv.models[ (int)e->v.modelindex]; // Mod_ForName (m, true); + + if (mod) + //johnfitz -- correct physics cullboxes for bmodels + { + if (mod->type == mod_brush) + SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true); + else + SetMinMaxSize (e, mod->mins, mod->maxs, true); + } + //johnfitz + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} + +/* +================= +PF_bprint + +broadcast print to everyone on server + +bprint(value) +================= +*/ +static void PF_bprint (void) +{ + char *s; + + s = PF_VarString(0); + SV_BroadcastPrintf ("%s", s); +} + +/* +================= +PF_sprint + +single print to a specific client + +sprint(clientent, value) +================= +*/ +static void PF_sprint (void) +{ + char *s; + client_t *client; + int entnum; + + entnum = G_EDICTNUM(OFS_PARM0); + s = PF_VarString(1); + + if (entnum < 1 || entnum > svs.maxclients) + { + Con_Printf ("tried to sprint to a non-client\n"); + return; + } + + client = &svs.clients[entnum-1]; + + MSG_WriteChar (&client->message,svc_print); + MSG_WriteString (&client->message, s ); +} + + +/* +================= +PF_centerprint + +single print to a specific client + +centerprint(clientent, value) +================= +*/ +static void PF_centerprint (void) +{ + char *s; + client_t *client; + int entnum; + + entnum = G_EDICTNUM(OFS_PARM0); + s = PF_VarString(1); + + if (entnum < 1 || entnum > svs.maxclients) + { + Con_Printf ("tried to sprint to a non-client\n"); + return; + } + + client = &svs.clients[entnum-1]; + + MSG_WriteChar (&client->message,svc_centerprint); + MSG_WriteString (&client->message, s); +} + + +/* +================= +PF_normalize + +vector normalize(vector) +================= +*/ +static void PF_normalize (void) +{ + float *value1; + vec3_t newvalue; + double new_temp; + + value1 = G_VECTOR(OFS_PARM0); + + new_temp = (double)value1[0] * value1[0] + (double)value1[1] * value1[1] + (double)value1[2]*value1[2]; + new_temp = sqrt (new_temp); + + if (new_temp == 0) + newvalue[0] = newvalue[1] = newvalue[2] = 0; + else + { + new_temp = 1 / new_temp; + newvalue[0] = value1[0] * new_temp; + newvalue[1] = value1[1] * new_temp; + newvalue[2] = value1[2] * new_temp; + } + + VectorCopy (newvalue, G_VECTOR(OFS_RETURN)); +} + +/* +================= +PF_vlen + +scalar vlen(vector) +================= +*/ +static void PF_vlen (void) +{ + float *value1; + double new_temp; + + value1 = G_VECTOR(OFS_PARM0); + + new_temp = (double)value1[0] * value1[0] + (double)value1[1] * value1[1] + (double)value1[2]*value1[2]; + new_temp = sqrt(new_temp); + + G_FLOAT(OFS_RETURN) = new_temp; +} + +/* +================= +PF_vectoyaw + +float vectoyaw(vector) +================= +*/ +static void PF_vectoyaw (void) +{ + float *value1; + float yaw; + + value1 = 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; + } + + G_FLOAT(OFS_RETURN) = yaw; +} + + +/* +================= +PF_vectoangles + +vector vectoangles(vector) +================= +*/ +static void PF_vectoangles (void) +{ + float *value1; + float forward; + float yaw, pitch; + + value1 = G_VECTOR(OFS_PARM0); + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + G_FLOAT(OFS_RETURN+0) = pitch; + G_FLOAT(OFS_RETURN+1) = yaw; + G_FLOAT(OFS_RETURN+2) = 0; +} + +/* +================= +PF_Random + +Returns a number from 0 <= num < 1 + +random() +================= +*/ +static void PF_random (void) +{ + float num; + + num = (rand() & 0x7fff) / ((float)0x7fff); + + G_FLOAT(OFS_RETURN) = num; +} + +/* +================= +PF_particle + +particle(origin, color, count) +================= +*/ +static void PF_particle (void) +{ + float *org, *dir; + float color; + float count; + + org = G_VECTOR(OFS_PARM0); + dir = G_VECTOR(OFS_PARM1); + color = G_FLOAT(OFS_PARM2); + count = G_FLOAT(OFS_PARM3); + SV_StartParticle (org, dir, color, count); +} + + +/* +================= +PF_ambientsound + +================= +*/ +static void PF_ambientsound (void) +{ + const char *samp, **check; + float *pos; + float vol, attenuation; + int i, soundnum; + int large = false; //johnfitz -- PROTOCOL_FITZQUAKE + + pos = G_VECTOR (OFS_PARM0); + samp = G_STRING(OFS_PARM1); + vol = G_FLOAT(OFS_PARM2); + attenuation = G_FLOAT(OFS_PARM3); + +// check to see if samp was properly precached + for (soundnum = 0, check = sv.sound_precache; *check; check++, soundnum++) + { + if (!strcmp(*check, samp)) + break; + } + + if (!*check) + { + Con_Printf ("no precache: %s\n", samp); + return; + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (soundnum > 255) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + else + large = true; + } + //johnfitz + +// add an svc_spawnambient command to the level signon packet + + //johnfitz -- PROTOCOL_FITZQUAKE + if (large) + MSG_WriteByte (&sv.signon,svc_spawnstaticsound2); + else + MSG_WriteByte (&sv.signon,svc_spawnstaticsound); + //johnfitz + + for (i = 0; i < 3; i++) + MSG_WriteCoord(&sv.signon, pos[i], sv.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (large) + MSG_WriteShort(&sv.signon, soundnum); + else + MSG_WriteByte (&sv.signon, soundnum); + //johnfitz + + MSG_WriteByte (&sv.signon, vol*255); + MSG_WriteByte (&sv.signon, attenuation*64); + +} + +/* +================= +PF_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 PF_sound (void) +{ + const char *sample; + int channel; + edict_t *entity; + int volume; + float attenuation; + + entity = G_EDICT(OFS_PARM0); + channel = G_FLOAT(OFS_PARM1); + sample = G_STRING(OFS_PARM2); + volume = G_FLOAT(OFS_PARM3) * 255; + attenuation = G_FLOAT(OFS_PARM4); + + if (volume < 0 || volume > 255) + Host_Error ("SV_StartSound: volume = %i", volume); + + if (attenuation < 0 || attenuation > 4) + Host_Error ("SV_StartSound: attenuation = %f", attenuation); + + if (channel < 0 || channel > 7) + Host_Error ("SV_StartSound: channel = %i", channel); + + SV_StartSound (entity, channel, sample, volume, attenuation); +} + +/* +================= +PF_break + +break() +================= +*/ +static void PF_break (void) +{ + Con_Printf ("break statement\n"); + *(int *)-4 = 0; // dump to debugger +// PR_RunError ("break statement"); +} + +/* +================= +PF_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, tryents) +================= +*/ +static void PF_traceline (void) +{ + float *v1, *v2; + trace_t trace; + int nomonsters; + edict_t *ent; + + v1 = G_VECTOR(OFS_PARM0); + v2 = G_VECTOR(OFS_PARM1); + nomonsters = G_FLOAT(OFS_PARM2); + ent = G_EDICT(OFS_PARM3); + + /* FIXME FIXME FIXME: Why do we hit this with certain progs.dat ?? */ + if (developer.value) { + 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])) { + Con_Warning ("NAN in traceline:\nv1(%f %f %f) v2(%f %f %f)\nentity %d\n", + v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], NUM_FOR_EDICT(ent)); + } + } + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2])) + v1[0] = v1[1] = v1[2] = 0; + if (IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + v2[0] = v2[1] = v2[2] = 0; + + trace = SV_Move (v1, vec3_origin, vec3_origin, v2, nomonsters, ent); + + pr_global_struct->trace_allsolid = trace.allsolid; + pr_global_struct->trace_startsolid = trace.startsolid; + pr_global_struct->trace_fraction = trace.fraction; + pr_global_struct->trace_inwater = trace.inwater; + pr_global_struct->trace_inopen = trace.inopen; + VectorCopy (trace.endpos, pr_global_struct->trace_endpos); + VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); + pr_global_struct->trace_plane_dist = trace.plane.dist; + if (trace.ent) + pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); + else + pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); +} + +/* +================= +PF_checkpos + +Returns true if the given entity can move to the given position from it's +current position by walking or rolling. +FIXME: make work... +scalar checkpos (entity, vector) +================= +*/ +#if 0 +static void PF_checkpos (void) +{ +} +#endif + +//============================================================================ + +static byte *checkpvs; //ericw -- changed to malloc +static int checkpvs_capacity; + +static int PF_newcheckclient (int check) +{ + int i; + byte *pvs; + edict_t *ent; + mleaf_t *leaf; + vec3_t org; + int pvsbytes; + +// cycle to the next one + + if (check < 1) + check = 1; + if (check > svs.maxclients) + check = svs.maxclients; + + if (check == svs.maxclients) + i = 1; + else + i = check + 1; + + for ( ; ; i++) + { + if (i == svs.maxclients+1) + i = 1; + + ent = EDICT_NUM(i); + + if (i == check) + break; // didn't find anything else + + if (ent->free) + continue; + if (ent->v.health <= 0) + continue; + if ((int)ent->v.flags & FL_NOTARGET) + continue; + + // anything that is a client, or has a client as an enemy + break; + } + +// get the PVS for the entity + VectorAdd (ent->v.origin, ent->v.view_ofs, org); + leaf = Mod_PointInLeaf (org, sv.worldmodel); + pvs = Mod_LeafPVS (leaf, sv.worldmodel); + + pvsbytes = (sv.worldmodel->numleafs+7)>>3; + if (checkpvs == NULL || pvsbytes > checkpvs_capacity) + { + checkpvs_capacity = pvsbytes; + checkpvs = (byte *) realloc (checkpvs, checkpvs_capacity); + if (!checkpvs) + Sys_Error ("PF_newcheckclient: realloc() failed on %d bytes", checkpvs_capacity); + } + memcpy (checkpvs, pvs, pvsbytes); + + return i; +} + +/* +================= +PF_checkclient + +Returns a client (or object that has a client enemy) that would be a +valid target. + +If there are more than one valid options, 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 () +================= +*/ +#define MAX_CHECK 16 +static int c_invis, c_notvis; +static void PF_checkclient (void) +{ + edict_t *ent, *self; + mleaf_t *leaf; + int l; + vec3_t view; + +// find a new check if on a new frame + if (sv.time - sv.lastchecktime >= 0.1) + { + sv.lastcheck = PF_newcheckclient (sv.lastcheck); + sv.lastchecktime = sv.time; + } + +// return check if it might be visible + ent = EDICT_NUM(sv.lastcheck); + if (ent->free || ent->v.health <= 0) + { + RETURN_EDICT(sv.edicts); + return; + } + +// if current entity can't possibly see the check entity, return 0 + self = PROG_TO_EDICT(pr_global_struct->self); + VectorAdd (self->v.origin, self->v.view_ofs, view); + leaf = Mod_PointInLeaf (view, sv.worldmodel); + l = (leaf - sv.worldmodel->leafs) - 1; + if ( (l < 0) || !(checkpvs[l>>3] & (1 << (l & 7))) ) + { + c_notvis++; + RETURN_EDICT(sv.edicts); + return; + } + +// might be able to see it + c_invis++; + RETURN_EDICT(ent); +} + +//============================================================================ + + +/* +================= +PF_stuffcmd + +Sends text over to the client's execution buffer + +stuffcmd (clientent, value) +================= +*/ +static void PF_stuffcmd (void) +{ + int entnum; + const char *str; + client_t *old; + + entnum = G_EDICTNUM(OFS_PARM0); + if (entnum < 1 || entnum > svs.maxclients) + PR_RunError ("Parm 0 not a client"); + str = G_STRING(OFS_PARM1); + + old = host_client; + host_client = &svs.clients[entnum-1]; + Host_ClientCommands ("%s", str); + host_client = old; +} + +/* +================= +PF_localcmd + +Sends text over to the client's execution buffer + +localcmd (string) +================= +*/ +static void PF_localcmd (void) +{ + const char *str; + + str = G_STRING(OFS_PARM0); + Cbuf_AddText (str); +} + +/* +================= +PF_cvar + +float cvar (string) +================= +*/ +static void PF_cvar (void) +{ + const char *str; + + str = G_STRING(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = Cvar_VariableValue (str); +} + +/* +================= +PF_cvar_set + +float cvar (string) +================= +*/ +static void PF_cvar_set (void) +{ + const char *var, *val; + + var = G_STRING(OFS_PARM0); + val = G_STRING(OFS_PARM1); + + Cvar_Set (var, val); +} + +/* +================= +PF_findradius + +Returns a chain of entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static void PF_findradius (void) +{ + edict_t *ent, *chain; + float rad; + float *org; + vec3_t eorg; + int i, j; + + chain = (edict_t *)sv.edicts; + + org = G_VECTOR(OFS_PARM0); + rad = G_FLOAT(OFS_PARM1); + + ent = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + { + if (ent->free) + continue; + if (ent->v.solid == SOLID_NOT) + continue; + for (j = 0; j < 3; j++) + eorg[j] = org[j] - (ent->v.origin[j] + (ent->v.mins[j] + ent->v.maxs[j]) * 0.5); + if (VectorLength(eorg) > rad) + continue; + + ent->v.chain = EDICT_TO_PROG(chain); + chain = ent; + } + + RETURN_EDICT(chain); +} + +/* +========= +PF_dprint +========= +*/ +static void PF_dprint (void) +{ + Con_DPrintf ("%s",PF_VarString(0)); +} + +static void PF_ftos (void) +{ + float v; + char *s; + + v = G_FLOAT(OFS_PARM0); + s = PR_GetTempString(); + if (v == (int)v) + sprintf (s, "%d",(int)v); + else + sprintf (s, "%5.1f",v); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} + +static void PF_fabs (void) +{ + float v; + v = G_FLOAT(OFS_PARM0); + G_FLOAT(OFS_RETURN) = fabs(v); +} + +static void PF_vtos (void) +{ + char *s; + + s = PR_GetTempString(); + sprintf (s, "'%5.1f %5.1f %5.1f'", G_VECTOR(OFS_PARM0)[0], G_VECTOR(OFS_PARM0)[1], G_VECTOR(OFS_PARM0)[2]); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} + +static void PF_Spawn (void) +{ + edict_t *ed; + + ed = ED_Alloc(); + + RETURN_EDICT(ed); +} + +static void PF_Remove (void) +{ + edict_t *ed; + + ed = G_EDICT(OFS_PARM0); + ED_Free (ed); +} + + +// entity (entity start, .string field, string match) find = #5; +static void PF_Find (void) +{ + int e; + int f; + const char *s, *t; + edict_t *ed; + + e = G_EDICTNUM(OFS_PARM0); + f = G_INT(OFS_PARM1); + s = G_STRING(OFS_PARM2); + if (!s) + PR_RunError ("PF_Find: bad search string"); + + for (e++ ; e < sv.num_edicts ; e++) + { + ed = EDICT_NUM(e); + if (ed->free) + continue; + t = E_STRING(ed,f); + if (!t) + continue; + if (!strcmp(t,s)) + { + RETURN_EDICT(ed); + return; + } + } + + RETURN_EDICT(sv.edicts); +} + +static void PR_CheckEmptyString (const char *s) +{ + if (s[0] <= ' ') + PR_RunError ("Bad string"); +} + +static void PF_precache_file (void) +{ // precache_file is only used to copy files with qcc, it does nothing + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); +} + +static void PF_precache_sound (void) +{ + const char *s; + int i; + + if (sv.state != ss_loading) + PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); + + s = G_STRING(OFS_PARM0); + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + PR_CheckEmptyString (s); + + for (i = 0; i < MAX_SOUNDS; i++) + { + if (!sv.sound_precache[i]) + { + sv.sound_precache[i] = s; + return; + } + if (!strcmp(sv.sound_precache[i], s)) + return; + } + PR_RunError ("PF_precache_sound: overflow"); +} + +static void PF_precache_model (void) +{ + const char *s; + int i; + + if (sv.state != ss_loading) + PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); + + s = G_STRING(OFS_PARM0); + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + PR_CheckEmptyString (s); + + for (i = 0; i < MAX_MODELS; i++) + { + if (!sv.model_precache[i]) + { + sv.model_precache[i] = s; + sv.models[i] = Mod_ForName (s, true); + return; + } + if (!strcmp(sv.model_precache[i], s)) + return; + } + PR_RunError ("PF_precache_model: overflow"); +} + + +static void PF_coredump (void) +{ + ED_PrintEdicts (); +} + +static void PF_traceon (void) +{ + pr_trace = true; +} + +static void PF_traceoff (void) +{ + pr_trace = false; +} + +static void PF_eprint (void) +{ + ED_PrintNum (G_EDICTNUM(OFS_PARM0)); +} + +/* +=============== +PF_walkmove + +float(float yaw, float dist) walkmove +=============== +*/ +static void PF_walkmove (void) +{ + edict_t *ent; + float yaw, dist; + vec3_t move; + dfunction_t *oldf; + int oldself; + + ent = PROG_TO_EDICT(pr_global_struct->self); + yaw = G_FLOAT(OFS_PARM0); + dist = G_FLOAT(OFS_PARM1); + + if ( !( (int)ent->v.flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + G_FLOAT(OFS_RETURN) = 0; + 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 = pr_xfunction; + oldself = pr_global_struct->self; + + G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true); + + +// restore program state + pr_xfunction = oldf; + pr_global_struct->self = oldself; +} + +/* +=============== +PF_droptofloor + +void() droptofloor +=============== +*/ +static void PF_droptofloor (void) +{ + edict_t *ent; + vec3_t end; + trace_t trace; + + ent = PROG_TO_EDICT(pr_global_struct->self); + + VectorCopy (ent->v.origin, end); + end[2] -= 256; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.fraction == 1 || trace.allsolid) + G_FLOAT(OFS_RETURN) = 0; + else + { + VectorCopy (trace.endpos, ent->v.origin); + SV_LinkEdict (ent, false); + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + G_FLOAT(OFS_RETURN) = 1; + } +} + +/* +=============== +PF_lightstyle + +void(float style, string value) lightstyle +=============== +*/ +static void PF_lightstyle (void) +{ + int style; + const char *val; + client_t *client; + int j; + + style = G_FLOAT(OFS_PARM0); + val = G_STRING(OFS_PARM1); + +// bounds check to avoid clobbering sv struct + if (style < 0 || style >= MAX_LIGHTSTYLES) + { + Con_DWarning("PF_lightstyle: invalid style %d\n", style); + return; + } + +// change the string in sv + sv.lightstyles[style] = val; + +// 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->spawned) + { + MSG_WriteChar (&client->message, svc_lightstyle); + MSG_WriteChar (&client->message, style); + MSG_WriteString (&client->message, val); + } + } +} + +static void PF_rint (void) +{ + float f; + f = G_FLOAT(OFS_PARM0); + if (f > 0) + G_FLOAT(OFS_RETURN) = (int)(f + 0.5); + else + G_FLOAT(OFS_RETURN) = (int)(f - 0.5); +} + +static void PF_floor (void) +{ + G_FLOAT(OFS_RETURN) = floor(G_FLOAT(OFS_PARM0)); +} + +static void PF_ceil (void) +{ + G_FLOAT(OFS_RETURN) = ceil(G_FLOAT(OFS_PARM0)); +} + + +/* +============= +PF_checkbottom +============= +*/ +static void PF_checkbottom (void) +{ + edict_t *ent; + + ent = G_EDICT(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = SV_CheckBottom (ent); +} + +/* +============= +PF_pointcontents +============= +*/ +static void PF_pointcontents (void) +{ + float *v; + + v = G_VECTOR(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = SV_PointContents (v); +} + +/* +============= +PF_nextent + +entity nextent(entity) +============= +*/ +static void PF_nextent (void) +{ + int i; + edict_t *ent; + + i = G_EDICTNUM(OFS_PARM0); + while (1) + { + i++; + if (i == sv.num_edicts) + { + RETURN_EDICT(sv.edicts); + return; + } + ent = EDICT_NUM(i); + if (!ent->free) + { + RETURN_EDICT(ent); + return; + } + } +} + +/* +============= +PF_aim + +Pick a vector for the player to shoot along +vector aim(entity, missilespeed) +============= +*/ +cvar_t sv_aim = {"sv_aim", "1", CVAR_NONE}; // ericw -- turn autoaim off by default. was 0.93 +static void PF_aim (void) +{ + edict_t *ent, *check, *bestent; + vec3_t start, dir, end, bestdir; + int i, j; + trace_t tr; + float dist, bestdist; + float speed; + + ent = G_EDICT(OFS_PARM0); + speed = G_FLOAT(OFS_PARM1); + (void) speed; /* variable set but not used */ + + VectorCopy (ent->v.origin, start); + start[2] += 20; + +// try sending a trace straight + VectorCopy (pr_global_struct->v_forward, dir); + VectorMA (start, 2048, dir, end); + tr = SV_Move (start, vec3_origin, vec3_origin, end, false, ent); + if (tr.ent && tr.ent->v.takedamage == DAMAGE_AIM + && (!teamplay.value || ent->v.team <= 0 || ent->v.team != tr.ent->v.team) ) + { + VectorCopy (pr_global_struct->v_forward, G_VECTOR(OFS_RETURN)); + return; + } + +// try all possible entities + VectorCopy (dir, bestdir); + bestdist = sv_aim.value; + bestent = NULL; + + check = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, check = NEXT_EDICT(check) ) + { + if (check->v.takedamage != DAMAGE_AIM) + continue; + if (check == ent) + continue; + if (teamplay.value && ent->v.team > 0 && ent->v.team == check->v.team) + continue; // don't aim at teammate + for (j = 0; j < 3; j++) + end[j] = check->v.origin[j] + 0.5 * (check->v.mins[j] + check->v.maxs[j]); + VectorSubtract (end, start, dir); + VectorNormalize (dir); + dist = DotProduct (dir, pr_global_struct->v_forward); + if (dist < bestdist) + continue; // to far to turn + tr = SV_Move (start, vec3_origin, vec3_origin, end, false, ent); + if (tr.ent == check) + { // can shoot at this one + bestdist = dist; + bestent = check; + } + } + + if (bestent) + { + VectorSubtract (bestent->v.origin, ent->v.origin, dir); + dist = DotProduct (dir, pr_global_struct->v_forward); + VectorScale (pr_global_struct->v_forward, dist, end); + end[2] = dir[2]; + VectorNormalize (end); + VectorCopy (end, G_VECTOR(OFS_RETURN)); + } + else + { + VectorCopy (bestdir, G_VECTOR(OFS_RETURN)); + } +} + +/* +============== +PF_changeyaw + +This was a major timewaster in progs, so it was converted to C +============== +*/ +void PF_changeyaw (void) +{ + edict_t *ent; + float ideal, current, move, speed; + + ent = PROG_TO_EDICT(pr_global_struct->self); + current = anglemod( ent->v.angles[1] ); + ideal = ent->v.ideal_yaw; + speed = ent->v.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; + } + + ent->v.angles[1] = anglemod (current + move); +} + +/* +=============================================================================== + +MESSAGE WRITING + +=============================================================================== +*/ + +static sizebuf_t *WriteDest (void) +{ + int entnum; + int dest; + edict_t *ent; + + dest = G_FLOAT(OFS_PARM0); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + ent = PROG_TO_EDICT(pr_global_struct->msg_entity); + entnum = NUM_FOR_EDICT(ent); + if (entnum < 1 || entnum > svs.maxclients) + PR_RunError ("WriteDest: not a client"); + return &svs.clients[entnum-1].message; + + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return &sv.signon; + + default: + PR_RunError ("WriteDest: bad destination"); + break; + } + + return NULL; +} + +static void PF_WriteByte (void) +{ + MSG_WriteByte (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteChar (void) +{ + MSG_WriteChar (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteShort (void) +{ + MSG_WriteShort (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteLong (void) +{ + MSG_WriteLong (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteAngle (void) +{ + MSG_WriteAngle (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); +} + +static void PF_WriteCoord (void) +{ + MSG_WriteCoord (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); +} + +static void PF_WriteString (void) +{ + MSG_WriteString (WriteDest(), G_STRING(OFS_PARM1)); +} + +static void PF_WriteEntity (void) +{ + MSG_WriteShort (WriteDest(), G_EDICTNUM(OFS_PARM1)); +} + +//============================================================================= + +static void PF_makestatic (void) +{ + edict_t *ent; + int i; + int bits = 0; //johnfitz -- PROTOCOL_FITZQUAKE + + ent = G_EDICT(OFS_PARM0); + + //johnfitz -- don't send invisible static entities + if (ent->alpha == ENTALPHA_ZERO) { + ED_Free (ent); + return; + } + //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol == PROTOCOL_NETQUAKE) + { + if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00 || (int)(ent->v.frame) & 0xFF00) + { + ED_Free (ent); + return; //can't display the correct model & frame, so don't show it at all + } + } + else + { + if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00) + bits |= B_LARGEMODEL; + if ((int)(ent->v.frame) & 0xFF00) + bits |= B_LARGEFRAME; + if (ent->alpha != ENTALPHA_DEFAULT) + bits |= B_ALPHA; + } + + if (bits) + { + MSG_WriteByte (&sv.signon, svc_spawnstatic2); + MSG_WriteByte (&sv.signon, bits); + } + else + MSG_WriteByte (&sv.signon, svc_spawnstatic); + + if (bits & B_LARGEMODEL) + MSG_WriteShort (&sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); + else + MSG_WriteByte (&sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); + + if (bits & B_LARGEFRAME) + MSG_WriteShort (&sv.signon, ent->v.frame); + else + MSG_WriteByte (&sv.signon, ent->v.frame); + //johnfitz + + MSG_WriteByte (&sv.signon, ent->v.colormap); + MSG_WriteByte (&sv.signon, ent->v.skin); + for (i = 0; i < 3; i++) + { + MSG_WriteCoord(&sv.signon, ent->v.origin[i], sv.protocolflags); + MSG_WriteAngle(&sv.signon, ent->v.angles[i], sv.protocolflags); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & B_ALPHA) + MSG_WriteByte (&sv.signon, ent->alpha); + //johnfitz + +// throw the entity away now + ED_Free (ent); +} + +//============================================================================= + +/* +============== +PF_setspawnparms +============== +*/ +static void PF_setspawnparms (void) +{ + edict_t *ent; + int i; + client_t *client; + + ent = G_EDICT(OFS_PARM0); + i = NUM_FOR_EDICT(ent); + if (i < 1 || i > svs.maxclients) + PR_RunError ("Entity is not a client"); + + // copy spawn parms out of the client_t + client = svs.clients + (i-1); + + for (i = 0; i < NUM_SPAWN_PARMS; i++) + (&pr_global_struct->parm1)[i] = client->spawn_parms[i]; +} + +/* +============== +PF_changelevel +============== +*/ +static void PF_changelevel (void) +{ + const char *s; + +// make sure we don't issue two changelevels + if (svs.changelevel_issued) + return; + svs.changelevel_issued = true; + + s = G_STRING(OFS_PARM0); + Cbuf_AddText (va("changelevel %s\n",s)); +} + +static void PF_Fixme (void) +{ + PR_RunError ("unimplemented builtin"); +} + + +static builtin_t pr_builtin[] = +{ + PF_Fixme, + PF_makevectors, // void(entity e) makevectors = #1 + PF_setorigin, // void(entity e, vector o) setorigin = #2 + PF_setmodel, // void(entity e, string m) setmodel = #3 + PF_setsize, // void(entity e, vector min, vector max) setsize = #4 + PF_Fixme, // void(entity e, vector min, vector max) setabssize = #5 + PF_break, // void() break = #6 + PF_random, // float() random = #7 + PF_sound, // void(entity e, float chan, string samp) sound = #8 + PF_normalize, // vector(vector v) normalize = #9 + PF_error, // void(string e) error = #10 + PF_objerror, // void(string e) objerror = #11 + PF_vlen, // float(vector v) vlen = #12 + PF_vectoyaw, // float(vector v) vectoyaw = #13 + PF_Spawn, // entity() spawn = #14 + PF_Remove, // void(entity e) remove = #15 + PF_traceline, // float(vector v1, vector v2, float tryents) traceline = #16 + PF_checkclient, // entity() clientlist = #17 + PF_Find, // entity(entity start, .string fld, string match) find = #18 + PF_precache_sound, // void(string s) precache_sound = #19 + PF_precache_model, // void(string s) precache_model = #20 + PF_stuffcmd, // void(entity client, string s)stuffcmd = #21 + PF_findradius, // entity(vector org, float rad) findradius = #22 + PF_bprint, // void(string s) bprint = #23 + PF_sprint, // void(entity client, string s) sprint = #24 + PF_dprint, // void(string s) dprint = #25 + PF_ftos, // void(string s) ftos = #26 + PF_vtos, // void(string s) vtos = #27 + PF_coredump, + PF_traceon, + PF_traceoff, + PF_eprint, // void(entity e) debug print an entire entity + PF_walkmove, // float(float yaw, float dist) walkmove + PF_Fixme, // float(float yaw, float dist) walkmove + PF_droptofloor, + PF_lightstyle, + PF_rint, + PF_floor, + PF_ceil, + PF_Fixme, + PF_checkbottom, + PF_pointcontents, + PF_Fixme, + PF_fabs, + PF_aim, + PF_cvar, + PF_localcmd, + PF_nextent, + PF_particle, + PF_changeyaw, + PF_Fixme, + PF_vectoangles, + + PF_WriteByte, + PF_WriteChar, + PF_WriteShort, + PF_WriteLong, + PF_WriteCoord, + PF_WriteAngle, + PF_WriteString, + PF_WriteEntity, + + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + + SV_MoveToGoal, + PF_precache_file, + PF_makestatic, + + PF_changelevel, + PF_Fixme, + + PF_cvar_set, + PF_centerprint, + + PF_ambientsound, + + PF_precache_model, + PF_precache_sound, // precache_sound2 is different only for qcc + PF_precache_file, + + PF_setspawnparms +}; + +builtin_t *pr_builtins = pr_builtin; +int pr_numbuiltins = sizeof(pr_builtin)/sizeof(pr_builtin[0]); + diff --git a/source/pr_comp.h b/source/pr_comp.h new file mode 100644 index 0000000..bc6c62e --- /dev/null +++ b/source/pr_comp.h @@ -0,0 +1,197 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __PR_COMP_H +#define __PR_COMP_H + +// this file is shared by quake and qcc + +typedef int func_t; +typedef int string_t; + +typedef enum +{ + ev_bad = -1, + ev_void = 0, + 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 + + +enum +{ + 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 +}; + +typedef struct statement_s +{ + unsigned short op; + short a, b, c; +} dstatement_t; + +typedef struct +{ + unsigned short type; // if DEF_SAVEGLOBAL 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 +{ + 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; + byte parm_size[MAX_PARMS]; +} dfunction_t; + + +#define PROG_VERSION 6 +typedef struct +{ + 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 /* __PR_COMP_H */ + diff --git a/source/pr_edict.c b/source/pr_edict.c new file mode 100644 index 0000000..241c9bf --- /dev/null +++ b/source/pr_edict.c @@ -0,0 +1,1286 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_edict.c -- entity dictionary + +#include "quakedef.h" + +dprograms_t *progs; +dfunction_t *pr_functions; + +static char *pr_strings; +static int pr_stringssize; +static const char **pr_knownstrings; +static int pr_maxknownstrings; +static int pr_numknownstrings; +static ddef_t *pr_fielddefs; +static ddef_t *pr_globaldefs; + +qboolean pr_alpha_supported; //johnfitz + +dstatement_t *pr_statements; +globalvars_t *pr_global_struct; +float *pr_globals; // same as pr_global_struct +int pr_edict_size; // in bytes + +unsigned short pr_crc; + +int type_size[8] = { + 1, // ev_void + 1, // sizeof(string_t) / 4 // ev_string + 1, // ev_float + 3, // ev_vector + 1, // ev_entity + 1, // ev_field + 1, // sizeof(func_t) / 4 // ev_function + 1 // sizeof(void *) / 4 // ev_pointer +}; + +static ddef_t *ED_FieldAtOfs (int ofs); +static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s); + +#define MAX_FIELD_LEN 64 +#define GEFV_CACHESIZE 2 + +typedef struct { + ddef_t *pcache; + char field[MAX_FIELD_LEN]; +} gefv_cache; + +static gefv_cache gefvCache[GEFV_CACHESIZE] = +{ + { NULL, "" }, + { NULL, "" } +}; + +cvar_t nomonsters = {"nomonsters", "0", CVAR_NONE}; +cvar_t gamecfg = {"gamecfg", "0", CVAR_NONE}; +cvar_t scratch1 = {"scratch1", "0", CVAR_NONE}; +cvar_t scratch2 = {"scratch2", "0", CVAR_NONE}; +cvar_t scratch3 = {"scratch3", "0", CVAR_NONE}; +cvar_t scratch4 = {"scratch4", "0", CVAR_NONE}; +cvar_t savedgamecfg = {"savedgamecfg", "0", CVAR_ARCHIVE}; +cvar_t saved1 = {"saved1", "0", CVAR_ARCHIVE}; +cvar_t saved2 = {"saved2", "0", CVAR_ARCHIVE}; +cvar_t saved3 = {"saved3", "0", CVAR_ARCHIVE}; +cvar_t saved4 = {"saved4", "0", CVAR_ARCHIVE}; + +/* +================= +ED_ClearEdict + +Sets everything to NULL +================= +*/ +void ED_ClearEdict (edict_t *e) +{ + memset (&e->v, 0, progs->entityfields * 4); + e->free = false; +} + +/* +================= +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. +================= +*/ +edict_t *ED_Alloc (void) +{ + int i; + edict_t *e; + + for (i = svs.maxclients + 1; i < sv.num_edicts; i++) + { + e = EDICT_NUM(i); + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if (e->free && ( e->freetime < 2 || sv.time - e->freetime > 0.5 ) ) + { + ED_ClearEdict (e); + return e; + } + } + + if (i == sv.max_edicts) //johnfitz -- use sv.max_edicts instead of MAX_EDICTS + Host_Error ("ED_Alloc: no free edicts (max_edicts is %i)", sv.max_edicts); + + sv.num_edicts++; + e = EDICT_NUM(i); + memset(e, 0, pr_edict_size); // ericw -- switched sv.edicts to malloc(), so we are accessing uninitialized memory and must fully zero it, not just ED_ClearEdict + + return e; +} + +/* +================= +ED_Free + +Marks the edict as free +FIXME: walk all entities and NULL out references to this entity +================= +*/ +void ED_Free (edict_t *ed) +{ + SV_UnlinkEdict (ed); // unlink from world bsp + + ed->free = true; + ed->v.model = 0; + ed->v.takedamage = 0; + ed->v.modelindex = 0; + ed->v.colormap = 0; + ed->v.skin = 0; + ed->v.frame = 0; + VectorCopy (vec3_origin, ed->v.origin); + VectorCopy (vec3_origin, ed->v.angles); + ed->v.nextthink = -1; + ed->v.solid = 0; + ed->alpha = ENTALPHA_DEFAULT; //johnfitz -- reset alpha for next entity + + ed->freetime = sv.time; +} + +//=========================================================================== + +/* +============ +ED_GlobalAtOfs +============ +*/ +static ddef_t *ED_GlobalAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_globaldefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +ED_FieldAtOfs +============ +*/ +static ddef_t *ED_FieldAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numfielddefs; i++) + { + def = &pr_fielddefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +ED_FindField +============ +*/ +static ddef_t *ED_FindField (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numfielddefs; i++) + { + def = &pr_fielddefs[i]; + if ( !strcmp(PR_GetString(def->s_name), name) ) + return def; + } + return NULL; +} + + +/* +============ +ED_FindGlobal +============ +*/ +static ddef_t *ED_FindGlobal (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_globaldefs[i]; + if ( !strcmp(PR_GetString(def->s_name), name) ) + return def; + } + return NULL; +} + + +/* +============ +ED_FindFunction +============ +*/ +static dfunction_t *ED_FindFunction (const char *fn_name) +{ + dfunction_t *func; + int i; + + for (i = 0; i < progs->numfunctions; i++) + { + func = &pr_functions[i]; + if ( !strcmp(PR_GetString(func->s_name), fn_name) ) + return func; + } + return NULL; +} + +/* +============ +GetEdictFieldValue +============ +*/ +eval_t *GetEdictFieldValue(edict_t *ed, const char *field) +{ + ddef_t *def = NULL; + int i; + static int rep = 0; + + for (i = 0; i < GEFV_CACHESIZE; i++) + { + if (!strcmp(field, gefvCache[i].field)) + { + def = gefvCache[i].pcache; + goto Done; + } + } + + def = ED_FindField (field); + + if (strlen(field) < MAX_FIELD_LEN) + { + gefvCache[rep].pcache = def; + strcpy (gefvCache[rep].field, field); + rep ^= 1; + } + +Done: + if (!def) + return NULL; + + return (eval_t *)((char *)&ed->v + def->ofs*4); +} + + +/* +============ +PR_ValueString +(etype_t type, eval_t *val) + +Returns a string describing *data in a type specific manner +============= +*/ +static const char *PR_ValueString (int type, eval_t *val) +{ + static char line[512]; + ddef_t *def; + dfunction_t *f; + + type &= ~DEF_SAVEGLOBAL; + + switch (type) + { + case ev_string: + sprintf (line, "%s", PR_GetString(val->string)); + break; + case ev_entity: + sprintf (line, "entity %i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)) ); + break; + case ev_function: + f = pr_functions + val->function; + sprintf (line, "%s()", PR_GetString(f->s_name)); + break; + case ev_field: + def = ED_FieldAtOfs ( val->_int ); + sprintf (line, ".%s", PR_GetString(def->s_name)); + break; + case ev_void: + sprintf (line, "void"); + break; + case ev_float: + sprintf (line, "%5.1f", val->_float); + break; + case ev_vector: + sprintf (line, "'%5.1f %5.1f %5.1f'", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_pointer: + sprintf (line, "pointer"); + break; + default: + sprintf (line, "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PR_UglyValueString +(etype_t type, eval_t *val) + +Returns a string describing *data in a type specific manner +Easier to parse than PR_ValueString +============= +*/ +static const char *PR_UglyValueString (int type, eval_t *val) +{ + static char line[512]; + ddef_t *def; + dfunction_t *f; + + type &= ~DEF_SAVEGLOBAL; + + switch (type) + { + case ev_string: + sprintf (line, "%s", PR_GetString(val->string)); + break; + case ev_entity: + sprintf (line, "%i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict))); + break; + case ev_function: + f = pr_functions + val->function; + sprintf (line, "%s", PR_GetString(f->s_name)); + break; + case ev_field: + def = ED_FieldAtOfs ( val->_int ); + sprintf (line, "%s", PR_GetString(def->s_name)); + break; + case ev_void: + sprintf (line, "void"); + break; + case ev_float: + sprintf (line, "%f", val->_float); + break; + case ev_vector: + sprintf (line, "%f %f %f", val->vector[0], val->vector[1], val->vector[2]); + break; + default: + sprintf (line, "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PR_GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +const char *PR_GlobalString (int ofs) +{ + static char line[512]; + const char *s; + int i; + ddef_t *def; + void *val; + + val = (void *)&pr_globals[ofs]; + def = ED_GlobalAtOfs(ofs); + if (!def) + sprintf (line,"%i(?)", ofs); + else + { + s = PR_ValueString (def->type, (eval_t *)val); + sprintf (line,"%i(%s)%s", ofs, PR_GetString(def->s_name), s); + } + + i = strlen(line); + for ( ; i < 20; i++) + strcat (line, " "); + strcat (line, " "); + + return line; +} + +const char *PR_GlobalStringNoContents (int ofs) +{ + static char line[512]; + int i; + ddef_t *def; + + def = ED_GlobalAtOfs(ofs); + if (!def) + sprintf (line,"%i(?)", ofs); + else + sprintf (line,"%i(%s)", ofs, PR_GetString(def->s_name)); + + i = strlen(line); + for ( ; i < 20; i++) + strcat (line, " "); + strcat (line, " "); + + return line; +} + + +/* +============= +ED_Print + +For debugging +============= +*/ +void ED_Print (edict_t *ed) +{ + ddef_t *d; + int *v; + int i, j, l; + const char *name; + int type; + + if (ed->free) + { + Con_Printf ("FREE\n"); + return; + } + + Con_SafePrintf("\nEDICT %i:\n", NUM_FOR_EDICT(ed)); //johnfitz -- was Con_Printf + for (i = 1; i < progs->numfielddefs; i++) + { + d = &pr_fielddefs[i]; + name = PR_GetString(d->s_name); + l = strlen (name); + if (l > 1 && name[l - 2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)&ed->v + d->ofs*4); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + for (j = 0; j < type_size[type]; j++) + { + if (v[j]) + break; + } + if (j == type_size[type]) + continue; + + Con_SafePrintf ("%s", name); //johnfitz -- was Con_Printf + while (l++ < 15) + Con_SafePrintf (" "); //johnfitz -- was Con_Printf + + Con_SafePrintf ("%s\n", PR_ValueString(d->type, (eval_t *)v)); //johnfitz -- was Con_Printf + } +} + +/* +============= +ED_Write + +For savegames +============= +*/ +void ED_Write (FILE *f, edict_t *ed) +{ + ddef_t *d; + int *v; + int i, j; + const char *name; + int type; + + fprintf (f, "{\n"); + + if (ed->free) + { + fprintf (f, "}\n"); + return; + } + + for (i = 1; i < progs->numfielddefs; i++) + { + d = &pr_fielddefs[i]; + name = PR_GetString(d->s_name); + j = strlen (name); + if (j > 1 && name[j - 2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)&ed->v + d->ofs*4); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + for (j = 0; j < type_size[type]; j++) + { + if (v[j]) + break; + } + if (j == type_size[type]) + continue; + + fprintf (f, "\"%s\" ", name); + fprintf (f, "\"%s\"\n", PR_UglyValueString(d->type, (eval_t *)v)); + } + + //johnfitz -- save entity alpha manually when progs.dat doesn't know about alpha + if (!pr_alpha_supported && ed->alpha != ENTALPHA_DEFAULT) + fprintf (f, "\"alpha\" \"%f\"\n", ENTALPHA_TOSAVE(ed->alpha)); + //johnfitz + + fprintf (f, "}\n"); +} + +void ED_PrintNum (int ent) +{ + ED_Print (EDICT_NUM(ent)); +} + +/* +============= +ED_PrintEdicts + +For debugging, prints all the entities in the current server +============= +*/ +void ED_PrintEdicts (void) +{ + int i; + + if (!sv.active) + return; + + Con_Printf ("%i entities\n", sv.num_edicts); + for (i = 0; i < sv.num_edicts; i++) + ED_PrintNum (i); +} + +/* +============= +ED_PrintEdict_f + +For debugging, prints a single edicy +============= +*/ +static void ED_PrintEdict_f (void) +{ + int i; + + if (!sv.active) + return; + + i = Q_atoi (Cmd_Argv(1)); + if (i < 0 || i >= sv.num_edicts) + { + Con_Printf("Bad edict number\n"); + return; + } + ED_PrintNum (i); +} + +/* +============= +ED_Count + +For debugging +============= +*/ +static void ED_Count (void) +{ + edict_t *ent; + int i, active, models, solid, step; + + if (!sv.active) + return; + + active = models = solid = step = 0; + for (i = 0; i < sv.num_edicts; i++) + { + ent = EDICT_NUM(i); + if (ent->free) + continue; + active++; + if (ent->v.solid) + solid++; + if (ent->v.model) + models++; + if (ent->v.movetype == MOVETYPE_STEP) + step++; + } + + Con_Printf ("num_edicts:%3i\n", sv.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); +} + + +/* +============================================================================== + +ARCHIVING GLOBALS + +FIXME: need to tag constants, doesn't really work +============================================================================== +*/ + +/* +============= +ED_WriteGlobals +============= +*/ +void ED_WriteGlobals (FILE *f) +{ + ddef_t *def; + int i; + const char *name; + int type; + + fprintf (f, "{\n"); + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_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 = PR_GetString(def->s_name); + fprintf (f, "\"%s\" ", name); + fprintf (f, "\"%s\"\n", PR_UglyValueString(type, (eval_t *)&pr_globals[def->ofs])); + } + fprintf (f, "}\n"); +} + +/* +============= +ED_ParseGlobals +============= +*/ +const char *ED_ParseGlobals (const char *data) +{ + char keyname[64]; + ddef_t *key; + + while (1) + { + // parse key + data = COM_Parse (data); + if (com_token[0] == '}') + break; + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + q_strlcpy (keyname, com_token, sizeof(keyname)); + + // parse value + data = COM_Parse (data); + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + Host_Error ("ED_ParseEntity: closing brace without data"); + + key = ED_FindGlobal (keyname); + if (!key) + { + Con_Printf ("'%s' is not a global\n", keyname); + continue; + } + + if (!ED_ParseEpair ((void *)pr_globals, key, com_token)) + Host_Error ("ED_ParseGlobals: parse error"); + } + return data; +} + +//============================================================================ + + +/* +============= +ED_NewString +============= +*/ +static string_t ED_NewString (const char *string) +{ + char *new_p; + int i, l; + string_t num; + + l = strlen(string) + 1; + num = PR_AllocString (l, &new_p); + + for (i = 0; i < l; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return num; +} + + +/* +============= +ED_ParseEval + +Can parse either fields or globals +returns false if error +============= +*/ +static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) +{ + int i; + char string[128]; + ddef_t *def; + char *v, *w; + char *end; + void *d; + dfunction_t *func; + + d = (void *)((int *)base + key->ofs); + + switch (key->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + *(string_t *)d = ED_NewString(s); + break; + + case ev_float: + *(float *)d = atof (s); + break; + + case ev_vector: + q_strlcpy (string, s, sizeof(string)); + end = (char *)string + strlen(string); + v = string; + w = string; + + for (i = 0; i < 3 && (w <= end); i++) // ericw -- added (w <= end) check + { + // set v to the next space (or 0 byte), and change that char to a 0 byte + while (*v && *v != ' ') + v++; + *v = 0; + ((float *)d)[i] = atof (w); + w = v = v+1; + } + // ericw -- fill remaining elements to 0 in case we hit the end of string + // before reading 3 floats. + if (i < 3) + { + Con_DWarning ("Avoided reading garbage for \"%s\" \"%s\"\n", PR_GetString(key->s_name), s); + for (; i < 3; i++) + ((float *)d)[i] = 0.0f; + } + break; + + case ev_entity: + *(int *)d = EDICT_TO_PROG(EDICT_NUM(atoi (s))); + break; + + case ev_field: + def = ED_FindField (s); + if (!def) + { + //johnfitz -- HACK -- suppress error becuase fog/sky fields might not be mentioned in defs.qc + if (strncmp(s, "sky", 3) && strcmp(s, "fog")) + Con_DPrintf ("Can't find field %s\n", s); + return false; + } + *(int *)d = G_INT(def->ofs); + break; + + case ev_function: + func = ED_FindFunction (s); + if (!func) + { + Con_Printf ("Can't find function %s\n", s); + return false; + } + *(func_t *)d = func - pr_functions; + break; + + default: + break; + } + return true; +} + +/* +==================== +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 *ED_ParseEdict (const char *data, edict_t *ent) +{ + ddef_t *key; + char keyname[256]; + qboolean anglehack, init; + int n; + + init = false; + + // clear it + if (ent != sv.edicts) // hack + memset (&ent->v, 0, progs->entityfields * 4); + + // go through all the dictionary pairs + while (1) + { + // parse key + data = COM_Parse (data); + if (com_token[0] == '}') + break; + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + // anglehack is to allow QuakeEd to write single scalar angles + // and allow them to be turned into vectors. (FIXME...) + if (!strcmp(com_token, "angle")) + { + strcpy (com_token, "angles"); + anglehack = true; + } + else + anglehack = false; + + // FIXME: change light to _light to get rid of this hack + if (!strcmp(com_token, "light")) + strcpy (com_token, "light_lev"); // hack for single light def + + q_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 + data = COM_Parse (data); + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + Host_Error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + //johnfitz -- hack to support .alpha even when progs.dat doesn't know about it + if (!strcmp(keyname, "alpha")) + ent->alpha = ENTALPHA_ENCODE(atof(com_token)); + //johnfitz + + key = ED_FindField (keyname); + if (!key) + { + //johnfitz -- HACK -- suppress error becuase fog/sky/alpha fields might not be mentioned in defs.qc + if (strncmp(keyname, "sky", 3) && strcmp(keyname, "fog") && strcmp(keyname, "alpha")) + Con_DPrintf ("\"%s\" is not a field\n", keyname); //johnfitz -- was Con_Printf + continue; + } + + if (anglehack) + { + char temp[32]; + strcpy (temp, com_token); + sprintf (com_token, "0 %s 0", temp); + } + + if (!ED_ParseEpair ((void *)&ent->v, key, com_token)) + Host_Error ("ED_ParseEdict: parse error"); + } + + if (!init) + ent->free = true; + + return data; +} + + +/* +================ +ED_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +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 ED_CallSpawnFunctions () to let the objects initialize themselves. +================ +*/ +void ED_LoadFromFile (const char *data) +{ + dfunction_t *func; + edict_t *ent = NULL; + int inhibit = 0; + + pr_global_struct->time = sv.time; + + // parse ents + while (1) + { + // parse the opening brace + data = COM_Parse (data); + if (!data) + break; + if (com_token[0] != '{') + Host_Error ("ED_LoadFromFile: found %s when expecting {",com_token); + + if (!ent) + ent = EDICT_NUM(0); + else + ent = ED_Alloc (); + data = ED_ParseEdict (data, ent); + + // remove things from different skill levels or deathmatch + if (deathmatch.value) + { + if (((int)ent->v.spawnflags & SPAWNFLAG_NOT_DEATHMATCH)) + { + ED_Free (ent); + inhibit++; + continue; + } + } + else if ((current_skill == 0 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_EASY)) + || (current_skill == 1 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_MEDIUM)) + || (current_skill >= 2 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_HARD)) ) + { + ED_Free (ent); + inhibit++; + continue; + } + +// +// immediately call spawn function +// + if (!ent->v.classname) + { + Con_SafePrintf ("No classname for:\n"); //johnfitz -- was Con_Printf + ED_Print (ent); + ED_Free (ent); + continue; + } + + // look for the spawn function + func = ED_FindFunction ( PR_GetString(ent->v.classname) ); + + if (!func) + { + Con_SafePrintf ("No spawn function for:\n"); //johnfitz -- was Con_Printf + ED_Print (ent); + ED_Free (ent); + continue; + } + + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (func - pr_functions); + } + + Con_DPrintf ("%i entities inhibited\n", inhibit); +} + + +/* +=============== +PR_LoadProgs +=============== +*/ +void PR_LoadProgs (void) +{ + int i; + + // flush the non-C variable lookup cache + for (i = 0; i < GEFV_CACHESIZE; i++) + gefvCache[i].field[0] = 0; + + CRC_Init (&pr_crc); + + progs = (dprograms_t *)COM_LoadHunkFile ("progs.dat", NULL); + if (!progs) + Host_Error ("PR_LoadProgs: couldn't load progs.dat"); + Con_DPrintf ("Programs occupy %iK.\n", com_filesize/1024); + + for (i = 0; i < com_filesize; i++) + CRC_ProcessByte (&pr_crc, ((byte *)progs)[i]); + + // byte swap the header + for (i = 0; i < (int) sizeof(*progs) / 4; i++) + ((int *)progs)[i] = LittleLong ( ((int *)progs)[i] ); + + if (progs->version != PROG_VERSION) + Host_Error ("progs.dat has wrong version number (%i should be %i)", progs->version, PROG_VERSION); + if (progs->crc != PROGHEADER_CRC) + Host_Error ("progs.dat system vars have been modified, progdefs.h is out of date"); + + pr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions); + pr_strings = (char *)progs + progs->ofs_strings; + if (progs->ofs_strings + progs->numstrings >= com_filesize) + Host_Error ("progs.dat strings go past end of file\n"); + + // initialize the strings + pr_numknownstrings = 0; + pr_maxknownstrings = 0; + pr_stringssize = progs->numstrings; + if (pr_knownstrings) + Z_Free ((void *)pr_knownstrings); + pr_knownstrings = NULL; + PR_SetEngineString(""); + + pr_globaldefs = (ddef_t *)((byte *)progs + progs->ofs_globaldefs); + pr_fielddefs = (ddef_t *)((byte *)progs + progs->ofs_fielddefs); + pr_statements = (dstatement_t *)((byte *)progs + progs->ofs_statements); + + pr_global_struct = (globalvars_t *)((byte *)progs + progs->ofs_globals); + pr_globals = (float *)pr_global_struct; + + // byte swap the lumps + for (i = 0; i < progs->numstatements; i++) + { + pr_statements[i].op = LittleShort(pr_statements[i].op); + pr_statements[i].a = LittleShort(pr_statements[i].a); + pr_statements[i].b = LittleShort(pr_statements[i].b); + pr_statements[i].c = LittleShort(pr_statements[i].c); + } + + for (i = 0; i < progs->numfunctions; i++) + { + pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement); + pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start); + pr_functions[i].s_name = LittleLong (pr_functions[i].s_name); + pr_functions[i].s_file = LittleLong (pr_functions[i].s_file); + pr_functions[i].numparms = LittleLong (pr_functions[i].numparms); + pr_functions[i].locals = LittleLong (pr_functions[i].locals); + } + + for (i = 0; i < progs->numglobaldefs; i++) + { + pr_globaldefs[i].type = LittleShort (pr_globaldefs[i].type); + pr_globaldefs[i].ofs = LittleShort (pr_globaldefs[i].ofs); + pr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name); + } + + pr_alpha_supported = false; //johnfitz + + for (i = 0; i < progs->numfielddefs; i++) + { + pr_fielddefs[i].type = LittleShort (pr_fielddefs[i].type); + if (pr_fielddefs[i].type & DEF_SAVEGLOBAL) + Host_Error ("PR_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL"); + pr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs); + pr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name); + + //johnfitz -- detect alpha support in progs.dat + if (!strcmp(pr_strings + pr_fielddefs[i].s_name,"alpha")) + pr_alpha_supported = true; + //johnfitz + } + + for (i = 0; i < progs->numglobals; i++) + ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); + + pr_edict_size = progs->entityfields * 4 + sizeof(edict_t) - sizeof(entvars_t); + // round off to next highest whole word address (esp for Alpha) + // this ensures that pointers in the engine data area are always + // properly aligned + pr_edict_size += sizeof(void *) - 1; + pr_edict_size &= ~(sizeof(void *) - 1); +} + + +/* +=============== +PR_Init +=============== +*/ +void PR_Init (void) +{ + Cmd_AddCommand ("edict", ED_PrintEdict_f); + Cmd_AddCommand ("edicts", ED_PrintEdicts); + Cmd_AddCommand ("edictcount", ED_Count); + Cmd_AddCommand ("profile", PR_Profile_f); + Cvar_RegisterVariable (&nomonsters); + Cvar_RegisterVariable (&gamecfg); + Cvar_RegisterVariable (&scratch1); + Cvar_RegisterVariable (&scratch2); + Cvar_RegisterVariable (&scratch3); + Cvar_RegisterVariable (&scratch4); + Cvar_RegisterVariable (&savedgamecfg); + Cvar_RegisterVariable (&saved1); + Cvar_RegisterVariable (&saved2); + Cvar_RegisterVariable (&saved3); + Cvar_RegisterVariable (&saved4); +} + + +edict_t *EDICT_NUM(int n) +{ + if (n < 0 || n >= sv.max_edicts) + Host_Error ("EDICT_NUM: bad number %i", n); + return (edict_t *)((byte *)sv.edicts + (n)*pr_edict_size); +} + +int NUM_FOR_EDICT(edict_t *e) +{ + int b; + + b = (byte *)e - (byte *)sv.edicts; + b = b / pr_edict_size; + + if (b < 0 || b >= sv.num_edicts) + Host_Error ("NUM_FOR_EDICT: bad pointer"); + return b; +} + +//=========================================================================== + + +#define PR_STRING_ALLOCSLOTS 256 + +static void PR_AllocStringSlots (void) +{ + pr_maxknownstrings += PR_STRING_ALLOCSLOTS; + Con_DPrintf2("PR_AllocStringSlots: realloc'ing for %d slots\n", pr_maxknownstrings); + pr_knownstrings = (const char **) Z_Realloc ((void *)pr_knownstrings, pr_maxknownstrings * sizeof(char *)); +} + +const char *PR_GetString (int num) +{ + if (num >= 0 && num < pr_stringssize) + return pr_strings + num; + else if (num < 0 && num >= -pr_numknownstrings) + { + if (!pr_knownstrings[-1 - num]) + { + Host_Error ("PR_GetString: attempt to get a non-existant string %d\n", num); + return ""; + } + return pr_knownstrings[-1 - num]; + } + else + { + Host_Error("PR_GetString: invalid string offset %d\n", num); + return ""; + } +} + +int PR_SetEngineString (const char *s) +{ + int i; + + if (!s) + return 0; +#if 0 /* can't: sv.model_precache & sv.sound_precache points to pr_strings */ + if (s >= pr_strings && s <= pr_strings + pr_stringssize) + Host_Error("PR_SetEngineString: \"%s\" in pr_strings area\n", s); +#else + if (s >= pr_strings && s <= pr_strings + pr_stringssize - 2) + return (int)(s - pr_strings); +#endif + for (i = 0; i < pr_numknownstrings; i++) + { + if (pr_knownstrings[i] == s) + return -1 - i; + } + // new unknown engine string + //Con_DPrintf ("PR_SetEngineString: new engine string %p\n", s); +#if 0 + for (i = 0; i < pr_numknownstrings; i++) + { + if (!pr_knownstrings[i]) + break; + } +#endif +// if (i >= pr_numknownstrings) +// { + if (i >= pr_maxknownstrings) + PR_AllocStringSlots(); + pr_numknownstrings++; +// } + pr_knownstrings[i] = s; + return -1 - i; +} + +int PR_AllocString (int size, char **ptr) +{ + int i; + + if (!size) + return 0; + for (i = 0; i < pr_numknownstrings; i++) + { + if (!pr_knownstrings[i]) + break; + } +// if (i >= pr_numknownstrings) +// { + if (i >= pr_maxknownstrings) + PR_AllocStringSlots(); + pr_numknownstrings++; +// } + pr_knownstrings[i] = (char *)Hunk_AllocName(size, "string"); + if (ptr) + *ptr = (char *) pr_knownstrings[i]; + return -1 - i; +} + diff --git a/source/pr_exec.c b/source/pr_exec.c new file mode 100644 index 0000000..ad9503e --- /dev/null +++ b/source/pr_exec.c @@ -0,0 +1,654 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +typedef struct +{ + int s; + dfunction_t *f; +} prstack_t; + +#define MAX_STACK_DEPTH 64 /* was 32 */ +static prstack_t pr_stack[MAX_STACK_DEPTH]; +static int pr_depth; + +#define LOCALSTACK_SIZE 2048 +static int localstack[LOCALSTACK_SIZE]; +static int localstack_used; + +qboolean pr_trace; +dfunction_t *pr_xfunction; +int pr_xstatement; +int pr_argc; + +static const char *pr_opnames[] = +{ + "DONE", + + "MUL_F", + "MUL_V", + "MUL_FV", + "MUL_VF", + + "DIV", + + "ADD_F", + "ADD_V", + + "SUB_F", + "SUB_V", + + "EQ_F", + "EQ_V", + "EQ_S", + "EQ_E", + "EQ_FNC", + + "NE_F", + "NE_V", + "NE_S", + "NE_E", + "NE_FNC", + + "LE", + "GE", + "LT", + "GT", + + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + + "ADDRESS", + + "STORE_F", + "STORE_V", + "STORE_S", + "STORE_ENT", + "STORE_FLD", + "STORE_FNC", + + "STOREP_F", + "STOREP_V", + "STOREP_S", + "STOREP_ENT", + "STOREP_FLD", + "STOREP_FNC", + + "RETURN", + + "NOT_F", + "NOT_V", + "NOT_S", + "NOT_ENT", + "NOT_FNC", + + "IF", + "IFNOT", + + "CALL0", + "CALL1", + "CALL2", + "CALL3", + "CALL4", + "CALL5", + "CALL6", + "CALL7", + "CALL8", + + "STATE", + + "GOTO", + + "AND", + "OR", + + "BITAND", + "BITOR" +}; + +const char *PR_GlobalString (int ofs); +const char *PR_GlobalStringNoContents (int ofs); + + +//============================================================================= + +/* +================= +PR_PrintStatement +================= +*/ +static void PR_PrintStatement (dstatement_t *s) +{ + int i; + + if ((unsigned int)s->op < sizeof(pr_opnames)/sizeof(pr_opnames[0])) + { + Con_Printf("%s ", pr_opnames[s->op]); + i = strlen(pr_opnames[s->op]); + for ( ; i < 10; i++) + Con_Printf(" "); + } + + if (s->op == OP_IF || s->op == OP_IFNOT) + Con_Printf("%sbranch %i", PR_GlobalString(s->a), s->b); + else if (s->op == OP_GOTO) + { + Con_Printf("branch %i", s->a); + } + else if ((unsigned int)(s->op-OP_STORE_F) < 6) + { + Con_Printf("%s", PR_GlobalString(s->a)); + Con_Printf("%s", PR_GlobalStringNoContents(s->b)); + } + else + { + if (s->a) + Con_Printf("%s", PR_GlobalString(s->a)); + if (s->b) + Con_Printf("%s", PR_GlobalString(s->b)); + if (s->c) + Con_Printf("%s", PR_GlobalStringNoContents(s->c)); + } + Con_Printf("\n"); +} + +/* +============ +PR_StackTrace +============ +*/ +static void PR_StackTrace (void) +{ + int i; + dfunction_t *f; + + if (pr_depth == 0) + { + Con_Printf("\n"); + return; + } + + pr_stack[pr_depth].f = pr_xfunction; + for (i = pr_depth; i >= 0; i--) + { + f = pr_stack[i].f; + if (!f) + { + Con_Printf("\n"); + } + else + { + Con_Printf("%12s : %s\n", PR_GetString(f->s_file), PR_GetString(f->s_name)); + } + } +} + + +/* +============ +PR_Profile_f + +============ +*/ +void PR_Profile_f (void) +{ + int i, num; + int pmax; + dfunction_t *f, *best; + + if (!sv.active) + return; + + num = 0; + do + { + pmax = 0; + best = NULL; + for (i = 0; i < progs->numfunctions; i++) + { + f = &pr_functions[i]; + if (f->profile > pmax) + { + pmax = f->profile; + best = f; + } + } + if (best) + { + if (num < 10) + Con_Printf("%7i %s\n", best->profile, PR_GetString(best->s_name)); + num++; + best->profile = 0; + } + } while (best); +} + + +/* +============ +PR_RunError + +Aborts the currently executing function +============ +*/ +void PR_RunError (const char *error, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, error); + q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + + PR_PrintStatement(pr_statements + pr_xstatement); + PR_StackTrace(); + + Con_Printf("%s\n", string); + + pr_depth = 0; // dump the stack so host_error can shutdown functions + + Host_Error("Program error"); +} + +/* +==================== +PR_EnterFunction + +Returns the new program statement counter +==================== +*/ +static int PR_EnterFunction (dfunction_t *f) +{ + int i, j, c, o; + + pr_stack[pr_depth].s = pr_xstatement; + pr_stack[pr_depth].f = pr_xfunction; + pr_depth++; + if (pr_depth >= MAX_STACK_DEPTH) + PR_RunError("stack overflow"); + + // save off any locals that the new function steps on + c = f->locals; + if (localstack_used + c > LOCALSTACK_SIZE) + PR_RunError("PR_ExecuteProgram: locals stack overflow\n"); + + for (i = 0; i < c ; i++) + localstack[localstack_used + i] = ((int *)pr_globals)[f->parm_start + i]; + localstack_used += c; + + // copy parameters + o = f->parm_start; + for (i = 0; i < f->numparms; i++) + { + for (j = 0; j < f->parm_size[i]; j++) + { + ((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0 + i*3 + j]; + o++; + } + } + + pr_xfunction = f; + return f->first_statement - 1; // offset the s++ +} + +/* +==================== +PR_LeaveFunction +==================== +*/ +static int PR_LeaveFunction (void) +{ + int i, c; + + if (pr_depth <= 0) + Host_Error("prog stack underflow"); + + // Restore locals from the stack + c = pr_xfunction->locals; + localstack_used -= c; + if (localstack_used < 0) + PR_RunError("PR_ExecuteProgram: locals stack underflow"); + + for (i = 0; i < c; i++) + ((int *)pr_globals)[pr_xfunction->parm_start + i] = localstack[localstack_used + i]; + + // up stack + pr_depth--; + pr_xfunction = pr_stack[pr_depth].f; + return pr_stack[pr_depth].s; +} + + +/* +==================== +PR_ExecuteProgram + +The interpretation main loop +==================== +*/ +#define OPA ((eval_t *)&pr_globals[(unsigned short)st->a]) +#define OPB ((eval_t *)&pr_globals[(unsigned short)st->b]) +#define OPC ((eval_t *)&pr_globals[(unsigned short)st->c]) + +void PR_ExecuteProgram (func_t fnum) +{ + eval_t *ptr; + dstatement_t *st; + dfunction_t *f, *newf; + int profile, startprofile; + edict_t *ed; + int exitdepth; + + if (!fnum || fnum >= progs->numfunctions) + { + if (pr_global_struct->self) + ED_Print (PROG_TO_EDICT(pr_global_struct->self)); + Host_Error ("PR_ExecuteProgram: NULL function"); + } + + f = &pr_functions[fnum]; + + pr_trace = false; + +// make a stack frame + exitdepth = pr_depth; + + st = &pr_statements[PR_EnterFunction(f)]; + startprofile = profile = 0; + + while (1) + { + st++; /* next statement */ + + if (++profile > 100000) + { + pr_xstatement = st - pr_statements; + PR_RunError("runaway loop error"); + } + + if (pr_trace) + PR_PrintStatement(st); + + 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: + OPC->_float = OPA->_float / OPB->_float; + 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 = OPA->_float && OPB->_float; + break; + case OP_OR: + OPC->_float = OPA->_float || OPB->_float; + break; + + case OP_NOT_F: + OPC->_float = !OPA->_float; + 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 || !*PR_GetString(OPA->string); + break; + case OP_NOT_FNC: + OPC->_float = !OPA->function; + break; + case OP_NOT_ENT: + OPC->_float = (PROG_TO_EDICT(OPA->edict) == sv.edicts); + 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(PR_GetString(OPA->string), PR_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(PR_GetString(OPA->string), PR_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->vector[0] = OPA->vector[0]; + OPB->vector[1] = OPA->vector[1]; + OPB->vector[2] = OPA->vector[2]; + break; + + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: // integers + case OP_STOREP_S: + case OP_STOREP_FNC: // pointers + ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_STOREP_V: + ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr->vector[0] = OPA->vector[0]; + ptr->vector[1] = OPA->vector[1]; + ptr->vector[2] = OPA->vector[2]; + break; + + case OP_ADDRESS: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + if (ed == (edict_t *)sv.edicts && sv.state == ss_active) + { + pr_xstatement = st - pr_statements; + PR_RunError("assignment to world entity"); + } + OPC->_int = (byte *)((int *)&ed->v + OPB->_int) - (byte *)sv.edicts; + break; + + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + OPC->_int = ((eval_t *)((int *)&ed->v + OPB->_int))->_int; + break; + + case OP_LOAD_V: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + ptr = (eval_t *)((int *)&ed->v + OPB->_int); + OPC->vector[0] = ptr->vector[0]; + OPC->vector[1] = ptr->vector[1]; + OPC->vector[2] = ptr->vector[2]; + break; + + case OP_IFNOT: + if (!OPA->_int) + st += st->b - 1; /* -1 to offset the st++ */ + break; + + case OP_IF: + if (OPA->_int) + st += st->b - 1; /* -1 to offset the st++ */ + break; + + case OP_GOTO: + st += st->a - 1; /* -1 to offset the st++ */ + 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: + pr_xfunction->profile += profile - startprofile; + startprofile = profile; + pr_xstatement = st - pr_statements; + pr_argc = st->op - OP_CALL0; + if (!OPA->function) + PR_RunError("NULL function"); + newf = &pr_functions[OPA->function]; + if (newf->first_statement < 0) + { // Built-in function + int i = -newf->first_statement; + if (i >= pr_numbuiltins) + PR_RunError("Bad builtin call number %d", i); + pr_builtins[i](); + break; + } + // Normal function + st = &pr_statements[PR_EnterFunction(newf)]; + break; + + case OP_DONE: + case OP_RETURN: + pr_xfunction->profile += profile - startprofile; + startprofile = profile; + pr_xstatement = st - pr_statements; + pr_globals[OFS_RETURN] = pr_globals[(unsigned short)st->a]; + pr_globals[OFS_RETURN + 1] = pr_globals[(unsigned short)st->a + 1]; + pr_globals[OFS_RETURN + 2] = pr_globals[(unsigned short)st->a + 2]; + st = &pr_statements[PR_LeaveFunction()]; + if (pr_depth == exitdepth) + { // Done + return; + } + break; + + case OP_STATE: + ed = PROG_TO_EDICT(pr_global_struct->self); + ed->v.nextthink = pr_global_struct->time + 0.1; + ed->v.frame = OPA->_float; + ed->v.think = OPB->function; + break; + + default: + pr_xstatement = st - pr_statements; + PR_RunError("Bad opcode %i", st->op); + } + } /* end of while(1) loop */ +} +#undef OPA +#undef OPB +#undef OPC + diff --git a/source/progdefs.h b/source/progdefs.h new file mode 100644 index 0000000..5a806f5 --- /dev/null +++ b/source/progdefs.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +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 __PROGDEFS_H +#define __PROGDEFS_H + +#include "progdefs.q1" + +#endif /* __PROGDEFS_H */ + diff --git a/source/progdefs.q1 b/source/progdefs.q1 new file mode 100644 index 0000000..eb15c45 --- /dev/null +++ b/source/progdefs.q1 @@ -0,0 +1,143 @@ + +/* file generated by qcc, do not modify */ + +typedef struct +{ 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 +{ + 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 diff --git a/source/progs.h b/source/progs.h new file mode 100644 index 0000000..c0e8066 --- /dev/null +++ b/source/progs.h @@ -0,0 +1,144 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_PROGS_H +#define _QUAKE_PROGS_H + +#include "pr_comp.h" /* defs shared with qcc */ +#include "progdefs.h" /* generated by program cdefs */ + +typedef union eval_s +{ + string_t string; + float _float; + float vector[3]; + func_t function; + int _int; + int edict; +} eval_t; + +#define MAX_ENT_LEAFS 32 +typedef struct edict_s +{ + qboolean free; + link_t area; /* linked to a division node or leaf */ + + int num_leafs; + int leafnums[MAX_ENT_LEAFS]; + + entity_state_t baseline; + unsigned char alpha; /* johnfitz -- hack to support alpha since it's not part of entvars_t */ + qboolean sendinterval; /* johnfitz -- send time until nextthink to client for better lerp timing */ + + float freetime; /* sv.time when the object was freed */ + entvars_t v; /* C exported fields from progs */ + + /* other fields from progs come immediately after */ +} edict_t; + +#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area) + +//============================================================================ + +extern dprograms_t *progs; +extern dfunction_t *pr_functions; +extern dstatement_t *pr_statements; +extern globalvars_t *pr_global_struct; +extern float *pr_globals; /* same as pr_global_struct */ + +extern int pr_edict_size; /* in bytes */ + + +void PR_Init (void); + +void PR_ExecuteProgram (func_t fnum); +void PR_LoadProgs (void); + +const char *PR_GetString (int num); +int PR_SetEngineString (const char *s); +int PR_AllocString (int bufferlength, char **ptr); + +void PR_Profile_f (void); + +edict_t *ED_Alloc (void); +void ED_Free (edict_t *ed); + +void ED_Print (edict_t *ed); +void ED_Write (FILE *f, edict_t *ed); +const char *ED_ParseEdict (const char *data, edict_t *ent); + +void ED_WriteGlobals (FILE *f); +const char *ED_ParseGlobals (const char *data); + +void ED_LoadFromFile (const char *data); + +/* +#define EDICT_NUM(n) ((edict_t *)(sv.edicts+ (n)*pr_edict_size)) +#define NUM_FOR_EDICT(e) (((byte *)(e) - sv.edicts) / pr_edict_size) +*/ +edict_t *EDICT_NUM(int n); +int NUM_FOR_EDICT(edict_t *e); + +#define NEXT_EDICT(e) ((edict_t *)( (byte *)e + pr_edict_size)) + +#define EDICT_TO_PROG(e) ((byte *)e - (byte *)sv.edicts) +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +#define G_FLOAT(o) (pr_globals[o]) +#define G_INT(o) (*(int *)&pr_globals[o]) +#define G_EDICT(o) ((edict_t *)((byte *)sv.edicts+ *(int *)&pr_globals[o])) +#define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o)) +#define G_VECTOR(o) (&pr_globals[o]) +#define G_STRING(o) (PR_GetString(*(string_t *)&pr_globals[o])) +#define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) + +#define E_FLOAT(e,o) (((float*)&e->v)[o]) +#define E_INT(e,o) (*(int *)&((float*)&e->v)[o]) +#define E_VECTOR(e,o) (&((float*)&e->v)[o]) +#define E_STRING(e,o) (PR_GetString(*(string_t *)&((float*)&e->v)[o])) + +extern int type_size[8]; + +typedef void (*builtin_t) (void); +extern builtin_t *pr_builtins; +extern int pr_numbuiltins; + +extern int pr_argc; + +extern qboolean pr_trace; +extern dfunction_t *pr_xfunction; +extern int pr_xstatement; + +extern unsigned short pr_crc; + +FUNC_NORETURN void PR_RunError (const char *error, ...) FUNC_PRINTF(1,2); +#ifdef __WATCOMC__ +#pragma aux PR_RunError aborts; +#endif + +void ED_PrintEdicts (void); +void ED_PrintNum (int ent); + +eval_t *GetEdictFieldValue(edict_t *ed, const char *field); + +#endif /* _QUAKE_PROGS_H */ + diff --git a/source/protocol.h b/source/protocol.h new file mode 100644 index 0000000..81c7e96 --- /dev/null +++ b/source/protocol.h @@ -0,0 +1,257 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_PROTOCOL_H +#define _QUAKE_PROTOCOL_H + +// protocol.h -- communications protocols + +#define PROTOCOL_NETQUAKE 15 //johnfitz -- standard quake protocol +#define PROTOCOL_FITZQUAKE 666 //johnfitz -- added new protocol for fitzquake 0.85 +#define PROTOCOL_RMQ 999 + +// PROTOCOL_RMQ protocol flags +#define PRFL_SHORTANGLE (1 << 1) +#define PRFL_FLOATANGLE (1 << 2) +#define PRFL_24BITCOORD (1 << 3) +#define PRFL_FLOATCOORD (1 << 4) +#define PRFL_EDICTSCALE (1 << 5) +#define PRFL_ALPHASANITY (1 << 6) // cleanup insanity with alpha +#define PRFL_INT32COORD (1 << 7) +#define PRFL_MOREFLAGS (1 << 31) // not supported + +// 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) +#define U_STEP (1<<5) //johnfitz -- was U_NOLERP, renamed since it's only used for MOVETYPE_STEP +#define U_FRAME (1<<6) +#define U_SIGNAL (1<<7) // just differentiates from other updates + +// svc_update can pass all of the fast update bits, plus more +#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) +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define U_EXTEND1 (1<<15) +#define U_ALPHA (1<<16) // 1 byte, uses ENTALPHA_ENCODE, not sent if equal to baseline +#define U_FRAME2 (1<<17) // 1 byte, this is .frame & 0xFF00 (second byte) +#define U_MODEL2 (1<<18) // 1 byte, this is .modelindex & 0xFF00 (second byte) +#define U_LERPFINISH (1<<19) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 0.1, this is ent->v.nextthink - sv.time, used for lerping +#define U_SCALE (1<<20) // 1 byte, for PROTOCOL_RMQ PRFL_EDICTSCALE, currently read but ignored +#define U_UNUSED21 (1<<21) +#define U_UNUSED22 (1<<22) +#define U_EXTEND2 (1<<23) // another byte to follow, future expansion +//johnfitz + +//johnfitz -- PROTOCOL_NEHAHRA transparency +#define U_TRANS (1<<15) +//johnfitz + +#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_UNUSED8 (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) +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define SU_EXTEND1 (1<<15) // another byte to follow +#define SU_WEAPON2 (1<<16) // 1 byte, this is .weaponmodel & 0xFF00 (second byte) +#define SU_ARMOR2 (1<<17) // 1 byte, this is .armorvalue & 0xFF00 (second byte) +#define SU_AMMO2 (1<<18) // 1 byte, this is .currentammo & 0xFF00 (second byte) +#define SU_SHELLS2 (1<<19) // 1 byte, this is .ammo_shells & 0xFF00 (second byte) +#define SU_NAILS2 (1<<20) // 1 byte, this is .ammo_nails & 0xFF00 (second byte) +#define SU_ROCKETS2 (1<<21) // 1 byte, this is .ammo_rockets & 0xFF00 (second byte) +#define SU_CELLS2 (1<<22) // 1 byte, this is .ammo_cells & 0xFF00 (second byte) +#define SU_EXTEND2 (1<<23) // another byte to follow +#define SU_WEAPONFRAME2 (1<<24) // 1 byte, this is .weaponframe & 0xFF00 (second byte) +#define SU_WEAPONALPHA (1<<25) // 1 byte, this is alpha for weaponmodel, uses ENTALPHA_ENCODE, not sent if ENTALPHA_DEFAULT +#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 +//johnfitz + +// 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 DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 + +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define SND_LARGEENTITY (1<<3) // a short + byte (instead of just a short) +#define SND_LARGESOUND (1<<4) // a short soundindex (instead of a byte) +//johnfitz + +//johnfitz -- PROTOCOL_FITZQUAKE -- flags for entity baseline messages +#define B_LARGEMODEL (1<<0) // modelindex is short instead of byte +#define B_LARGEFRAME (1<<1) // frame is short instead of byte +#define B_ALPHA (1<<2) // 1 byte, uses ENTALPHA_ENCODE, not sent if ENTALPHA_DEFAULT +//johnfitz + +//johnfitz -- PROTOCOL_FITZQUAKE -- alpha encoding +#define ENTALPHA_DEFAULT 0 //entity's alpha is "default" (i.e. water obeys r_wateralpha) -- must be zero so zeroed out memory works +#define ENTALPHA_ZERO 1 //entity is invisible (lowest possible alpha) +#define ENTALPHA_ONE 255 //entity is fully opaque (highest possible alpha) +#define ENTALPHA_ENCODE(a) (((a)==0)?ENTALPHA_DEFAULT:Q_rint(CLAMP(1,(a)*254.0f+1,255))) //server convert to byte to send to client +#define ENTALPHA_DECODE(a) (((a)==ENTALPHA_DEFAULT)?1.0f:((float)(a)-1)/(254)) //client convert to float for rendering +#define ENTALPHA_TOSAVE(a) (((a)==ENTALPHA_DEFAULT)?0.0f:(((a)==ENTALPHA_ZERO)?-1.0f:((float)(a)-1)/(254))) //server convert to float for savegame +//johnfitz + +// 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 +//#define 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 + +//johnfitz -- PROTOCOL_FITZQUAKE -- new server messages +#define svc_skybox 37 // [string] name +#define svc_bf 40 +#define svc_fog 41 // [byte] density [byte] red [byte] green [byte] blue [float] time +#define svc_spawnbaseline2 42 // support for large modelindex, large framenum, alpha, using flags +#define svc_spawnstatic2 43 // support for large modelindex, large framenum, alpha, using flags +#define svc_spawnstaticsound2 44 // [coord3] [short] samp [byte] vol [byte] aten +//johnfitz + +// +// 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 + +// +// temp entity events +// +#define TE_SPIKE 0 +#define TE_SUPERSPIKE 1 +#define TE_GUNSHOT 2 +#define TE_EXPLOSION 3 +#define TE_TAREXPLOSION 4 +#define TE_LIGHTNING1 5 +#define TE_LIGHTNING2 6 +#define TE_WIZSPIKE 7 +#define TE_KNIGHTSPIKE 8 +#define TE_LIGHTNING3 9 +#define TE_LAVASPLASH 10 +#define TE_TELEPORT 11 +#define TE_EXPLOSION2 12 + +// PGM 01/21/97 +#define TE_BEAM 13 +// PGM 01/21/97 + +typedef struct +{ + vec3_t origin; + vec3_t angles; + unsigned short modelindex; //johnfitz -- was int + unsigned short frame; //johnfitz -- was int + unsigned char colormap; //johnfitz -- was int + unsigned char skin; //johnfitz -- was int + unsigned char alpha; //johnfitz -- added + int effects; +} entity_state_t; + +typedef struct +{ + vec3_t viewangles; + +// intended velocities + float forwardmove; + float sidemove; + float upmove; +} usercmd_t; + +#endif /* _QUAKE_PROTOCOL_H */ + diff --git a/source/q_ctype.h b/source/q_ctype.h new file mode 100644 index 0000000..729f796 --- /dev/null +++ b/source/q_ctype.h @@ -0,0 +1,99 @@ +/* Locale insensitive ctype.h functions taken from the RPM library - + * RPM is Copyright (c) 1998 by Red Hat 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef Q_CTYPE_H +#define Q_CTYPE_H + +static inline int q_isascii(int c) +{ + return ((c & ~0x7f) == 0); +} + +static inline int q_islower(int c) +{ + return (c >= 'a' && c <= 'z'); +} + +static inline int q_isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +static inline int q_isalpha(int c) +{ + return (q_islower(c) || q_isupper(c)); +} + +static inline int q_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +static inline int q_isxdigit(int c) +{ + return (q_isdigit(c) || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')); +} + +static inline int q_isalnum(int c) +{ + return (q_isalpha(c) || q_isdigit(c)); +} + +static inline int q_isblank(int c) +{ + return (c == ' ' || c == '\t'); +} + +static inline int q_isspace(int c) +{ + switch(c) { + case ' ': case '\t': + case '\n': case '\r': + case '\f': case '\v': return 1; + } + return 0; +} + +static inline int q_isgraph(int c) +{ + return (c > 0x20 && c <= 0x7e); +} + +static inline int q_isprint(int c) +{ + return (c >= 0x20 && c <= 0x7e); +} + +static inline int q_toascii(int c) +{ + return (c & 0x7f); +} + +static inline int q_tolower(int c) +{ + return ((q_isupper(c)) ? (c | ('a' - 'A')) : c); +} + +static inline int q_toupper(int c) +{ + return ((q_islower(c)) ? (c & ~('a' - 'A')) : c); +} + +#endif /* Q_CTYPE_H */ diff --git a/source/q_sound.h b/source/q_sound.h new file mode 100644 index 0000000..ded7b5d --- /dev/null +++ b/source/q_sound.h @@ -0,0 +1,190 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +// sound.h -- client sound i/o functions + +#ifndef __QUAKE_SOUND__ +#define __QUAKE_SOUND__ + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +typedef struct sfx_s +{ + char name[MAX_QPATH]; + cache_user_t cache; +} sfx_t; + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + int length; + int loopstart; + int speed; + int width; + int stereo; + byte data[1]; /* variable sized */ +} sfxcache_t; + +typedef struct +{ + int channels; + int samples; /* mono samples in buffer */ + int submission_chunk; /* don't mix less than this # */ + int samplepos; /* in mono samples */ + int samplebits; + int signed8; /* device opened for S8 format? (e.g. Amiga AHI) */ + int speed; + unsigned char *buffer; +} dma_t; + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + sfx_t *sfx; /* sfx number */ + int leftvol; /* 0-255 volume */ + int rightvol; /* 0-255 volume */ + int end; /* end time in global paintsamples */ + int pos; /* sample position in sfx */ + int looping; /* where to loop, -1 = no looping */ + 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) */ + int master_vol; /* 0-255 master volume */ +} channel_t; + +#define WAV_FORMAT_PCM 1 + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; /* chunk starts this many bytes from file start */ +} wavinfo_t; + +void S_Init (void); +void S_Startup (void); +void S_Shutdown (void); +void S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation); +void S_StopSound (int entnum, int entchannel); +void S_StopAllSounds(qboolean clear); +void S_ClearBuffer (void); +void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up); +void S_ExtraUpdate (void); + +void S_BlockSound (void); +void S_UnblockSound (void); + +sfx_t *S_PrecacheSound (const char *sample); +void S_TouchSound (const char *sample); +void S_ClearPrecache (void); +void S_BeginPrecaching (void); +void S_EndPrecaching (void); +void S_PaintChannels (int endtime); +void S_InitPaintChannels (void); + +/* picks a channel based on priorities, empty slots, number of channels */ +channel_t *SND_PickChannel (int entnum, int entchannel); + +/* spatializes a channel */ +void SND_Spatialize (channel_t *ch); + +/* music stream support */ +void S_RawSamples(int samples, int rate, int width, int channels, byte * data, float volume); + /* Expects data in signed 16 bit, or unsigned 8 bit format. */ + +/* initializes cycling through a DMA buffer and returns information on it */ +qboolean SNDDMA_Init(dma_t *dma); + +/* gets the current DMA position */ +int SNDDMA_GetDMAPos(void); + +/* shutdown the DMA xfer. */ +void SNDDMA_Shutdown(void); + +/* validates & locks the dma buffer */ +void SNDDMA_LockBuffer(void); + +/* unlocks the dma buffer / sends sound to the device */ +void SNDDMA_Submit(void); + +/* blocks sound output upon window focus loss */ +void SNDDMA_BlockSound(void); + +/* unblocks the output upon window focus gain */ +void SNDDMA_UnblockSound(void); + +/* ==================================================================== + * User-setable variables + * ==================================================================== + */ + +#define MAX_CHANNELS 1024 // ericw -- was 512 /* johnfitz -- was 128 */ +#define MAX_DYNAMIC_CHANNELS 128 /* johnfitz -- was 8 */ + +extern channel_t snd_channels[MAX_CHANNELS]; +/* 0 to MAX_DYNAMIC_CHANNELS-1 = normal entity sounds + * MAX_DYNAMIC_CHANNELS to MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS -1 = water, etc + * MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS to total_channels = static sounds + */ + +extern volatile dma_t *shm; + +extern int total_channels; +extern int soundtime; +extern int paintedtime; +extern int s_rawend; + +extern vec3_t listener_origin; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; + +extern cvar_t sndspeed; +extern cvar_t snd_mixspeed; +extern cvar_t snd_filterquality; +extern cvar_t sfxvolume; +extern cvar_t loadas8bit; + +#define MAX_RAW_SAMPLES 8192 +extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + +extern cvar_t bgmvolume; + +void S_LocalSound (const char *name); +sfxcache_t *S_LoadSound (sfx_t *s); + +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength); + +void SND_InitScaletable (void); + +#endif /* __QUAKE_SOUND__ */ + diff --git a/source/q_stdinc.h b/source/q_stdinc.h new file mode 100644 index 0000000..84e423d --- /dev/null +++ b/source/q_stdinc.h @@ -0,0 +1,245 @@ +/* + * q_stdinc.h - includes the minimum necessary stdc headers, + * defines common and / or missing types. + * + * NOTE: for net stuff use net_sys.h, + * for byte order use q_endian.h, + * for math stuff use mathlib.h, + * for locale-insensitive ctype.h functions use q_ctype.h. + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Copyright (C) 2007-2011 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __QSTDINC_H +#define __QSTDINC_H + +#include +#include +#include +#ifndef _WIN32 /* others we support without sys/param.h? */ +#include +#endif + +#include + +/* NOTES on TYPE SIZES: + Quake/Hexen II engine relied on 32 bit int type size + with ILP32 (not LP32) model in mind. We now support + LP64 and LLP64, too. We expect: + sizeof (char) == 1 + sizeof (short) == 2 + sizeof (int) == 4 + sizeof (float) == 4 + sizeof (long) == 4 / 8 + sizeof (pointer *) == 4 / 8 + For this, we need stdint.h (or inttypes.h) + FIXME: On some platforms, only inttypes.h is available. + FIXME: Properly replace certain short and int usage + with int16_t and int32_t. + */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +/* MS Visual Studio provides stdint.h only starting with + * version 2010. Even in VS2010, there is no inttypes.h.. */ +#include "msinttypes/stdint.h" +#else +#include +#endif + +#include +#include +#include + +/*==========================================================================*/ + +#ifndef NULL +#if defined(__cplusplus) +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#define Q_MAXCHAR ((char)0x7f) +#define Q_MAXSHORT ((short)0x7fff) +#define Q_MAXINT ((int)0x7fffffff) +#define Q_MAXLONG ((int)0x7fffffff) +#define Q_MAXFLOAT ((int)0x7fffffff) + +#define Q_MINCHAR ((char)0x80) +#define Q_MINSHORT ((short)0x8000) +#define Q_MININT ((int)0x80000000) +#define Q_MINLONG ((int)0x80000000) +#define Q_MINFLOAT ((int)0x7fffffff) + +/* Make sure the types really have the right + * sizes: These macros are from SDL headers. + */ +#define COMPILE_TIME_ASSERT(name, x) \ + typedef int dummy_ ## name[(x) * 2 - 1] + +COMPILE_TIME_ASSERT(char, sizeof(char) == 1); +COMPILE_TIME_ASSERT(float, sizeof(float) == 4); +COMPILE_TIME_ASSERT(long, sizeof(long) >= 4); +COMPILE_TIME_ASSERT(int, sizeof(int) == 4); +COMPILE_TIME_ASSERT(short, sizeof(short) == 2); + +/* make sure enums are the size of ints for structure packing */ +typedef enum { + THE_DUMMY_VALUE +} THE_DUMMY_ENUM; +COMPILE_TIME_ASSERT(enum, sizeof(THE_DUMMY_ENUM) == sizeof(int)); + + +/* Provide a substitute for offsetof() if we don't have one. + * This variant works on most (but not *all*) systems... + */ +#ifndef offsetof +#define offsetof(t,m) ((size_t)&(((t *)0)->m)) +#endif + + +/*==========================================================================*/ + +typedef unsigned char byte; + +#undef true +#undef false +#if defined(__cplusplus) +/* some structures have qboolean members and the x86 asm code expect + * those members to be 4 bytes long. therefore, qboolean must be 32 + * bits and it can NOT be binary compatible with the 8 bit C++ bool. */ +typedef int qboolean; +COMPILE_TIME_ASSERT(falsehood, (0 == false)); +COMPILE_TIME_ASSERT(truth, (1 == true)); +#else +typedef enum { + false = 0, + true = 1 +} qboolean; +COMPILE_TIME_ASSERT(falsehood, ((1 != 1) == false)); +COMPILE_TIME_ASSERT(truth, ((1 == 1) == true)); +#endif +COMPILE_TIME_ASSERT(qboolean, sizeof(qboolean) == 4); + +/*==========================================================================*/ + +/* math */ +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + + +/*==========================================================================*/ + +/* MAX_OSPATH (max length of a filesystem pathname, i.e. PATH_MAX) + * Note: See GNU Hurd and others' notes about brokenness of this: + * http://www.gnu.org/software/hurd/community/gsoc/project_ideas/maxpath.html + * http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html */ + +#if !defined(PATH_MAX) +/* equivalent values? */ +#if defined(MAXPATHLEN) +#define PATH_MAX MAXPATHLEN +#elif defined(_WIN32) && defined(_MAX_PATH) +#define PATH_MAX _MAX_PATH +#elif defined(_WIN32) && defined(MAX_PATH) +#define PATH_MAX MAX_PATH +#else /* fallback */ +#define PATH_MAX 1024 +#endif +#endif /* PATH_MAX */ + +#define MAX_OSPATH PATH_MAX + +/*==========================================================================*/ + +/* missing types */ +#if defined(_MSC_VER) +#if defined(_WIN64) +#define ssize_t SSIZE_T +#else +typedef int ssize_t; +#endif /* _WIN64 */ +#endif /* _MSC_VER */ + +/*==========================================================================*/ + +/* function attributes, etc */ + +#if defined(__GNUC__) +#define FUNC_PRINTF(x,y) __attribute__((__format__(__printf__,x,y))) +#else +#define FUNC_PRINTF(x,y) +#endif + +/* argument format attributes for function pointers are supported for gcc >= 3.1 */ +#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0)) +#define FUNCP_PRINTF FUNC_PRINTF +#else +#define FUNCP_PRINTF(x,y) +#endif + +/* llvm's optnone function attribute started with clang-3.5.0 */ +#if defined(__clang__) && \ + (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 5)) +#define FUNC_NO_OPTIMIZE __attribute__((__optnone__)) +/* function optimize attribute is added starting with gcc 4.4.0 */ +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 3)) +#define FUNC_NO_OPTIMIZE __attribute__((__optimize__("0"))) +#else +#define FUNC_NO_OPTIMIZE +#endif + +#if defined(__GNUC__) +#define FUNC_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +#define FUNC_NORETURN __declspec(noreturn) +#elif defined(__WATCOMC__) +#define FUNC_NORETURN /* use the 'aborts' aux pragma */ +#else +#define FUNC_NORETURN +#endif + +#if defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define FUNC_NOINLINE __attribute__((__noinline__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1300) +#define FUNC_NOINLINE __declspec(noinline) +#else +#define FUNC_NOINLINE +#endif + +#if defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define FUNC_NOCLONE __attribute__((__noclone__)) +#else +#define FUNC_NOCLONE +#endif + +#if defined(_MSC_VER) && !defined(__cplusplus) +#define inline __inline +#endif /* _MSC_VER */ + +/*==========================================================================*/ + + +#endif /* __QSTDINC_H */ + diff --git a/source/qs_bmp.h b/source/qs_bmp.h new file mode 100644 index 0000000..fd2b2b5 --- /dev/null +++ b/source/qs_bmp.h @@ -0,0 +1,189 @@ +0x42, 0x4d, 0xc6, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x02, 0x00, 0x00, 0x28, 0x00, +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0xa4, 0x00, +0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x00, 0x05, 0x06, 0x07, 0x00, 0x06, 0x06, +0x07, 0x00, 0x07, 0x06, 0x07, 0x00, 0x06, 0x07, 0x06, 0x00, 0x06, 0x07, 0x07, 0x00, 0x07, 0x07, +0x07, 0x00, 0x07, 0x08, 0x07, 0x00, 0x08, 0x08, 0x08, 0x00, 0x07, 0x08, 0x09, 0x00, 0x08, 0x08, +0x09, 0x00, 0x07, 0x09, 0x08, 0x00, 0x08, 0x09, 0x08, 0x00, 0x09, 0x09, 0x08, 0x00, 0x08, 0x09, +0x09, 0x00, 0x09, 0x09, 0x09, 0x00, 0x08, 0x09, 0x0a, 0x00, 0x09, 0x09, 0x0a, 0x00, 0x09, 0x09, +0x0b, 0x00, 0x09, 0x0a, 0x09, 0x00, 0x08, 0x0a, 0x0a, 0x00, 0x09, 0x0a, 0x0a, 0x00, 0x0a, 0x0a, +0x0a, 0x00, 0x09, 0x0a, 0x0b, 0x00, 0x0a, 0x0a, 0x0b, 0x00, 0x09, 0x0a, 0x0c, 0x00, 0x09, 0x0b, +0x0b, 0x00, 0x0a, 0x0b, 0x0b, 0x00, 0x0a, 0x0b, 0x0c, 0x00, 0x0b, 0x0b, 0x0c, 0x00, 0x0a, 0x0b, +0x0d, 0x00, 0x0b, 0x0c, 0x0c, 0x00, 0x0b, 0x0c, 0x0d, 0x00, 0x0a, 0x0c, 0x0e, 0x00, 0x0b, 0x0c, +0x0e, 0x00, 0x0c, 0x0c, 0x0f, 0x00, 0x0b, 0x0d, 0x0d, 0x00, 0x0b, 0x0d, 0x0e, 0x00, 0x0c, 0x0d, +0x0e, 0x00, 0x0c, 0x0d, 0x0f, 0x00, 0x0d, 0x0d, 0x0f, 0x00, 0x0b, 0x0d, 0x10, 0x00, 0x0c, 0x0d, +0x10, 0x00, 0x0c, 0x0e, 0x0f, 0x00, 0x0d, 0x0e, 0x0f, 0x00, 0x0c, 0x0e, 0x10, 0x00, 0x0d, 0x0e, +0x10, 0x00, 0x0b, 0x0e, 0x11, 0x00, 0x0c, 0x0e, 0x11, 0x00, 0x0d, 0x0e, 0x11, 0x00, 0x0d, 0x0f, +0x10, 0x00, 0x0d, 0x0f, 0x11, 0x00, 0x0e, 0x0f, 0x11, 0x00, 0x0d, 0x0f, 0x12, 0x00, 0x0e, 0x0f, +0x12, 0x00, 0x0d, 0x0f, 0x13, 0x00, 0x0e, 0x10, 0x11, 0x00, 0x0d, 0x10, 0x12, 0x00, 0x0b, 0x10, +0x13, 0x00, 0x0e, 0x10, 0x12, 0x00, 0x0e, 0x10, 0x13, 0x00, 0x0d, 0x10, 0x14, 0x00, 0x0e, 0x10, +0x14, 0x00, 0x0d, 0x11, 0x13, 0x00, 0x0e, 0x11, 0x13, 0x00, 0x0f, 0x11, 0x13, 0x00, 0x0e, 0x11, +0x14, 0x00, 0x0f, 0x11, 0x14, 0x00, 0x0f, 0x11, 0x15, 0x00, 0x0f, 0x12, 0x14, 0x00, 0x0f, 0x12, +0x15, 0x00, 0x10, 0x12, 0x15, 0x00, 0x0f, 0x12, 0x16, 0x00, 0x10, 0x12, 0x16, 0x00, 0x0f, 0x12, +0x17, 0x00, 0x0f, 0x13, 0x16, 0x00, 0x10, 0x13, 0x16, 0x00, 0x10, 0x13, 0x17, 0x00, 0x11, 0x13, +0x17, 0x00, 0x11, 0x13, 0x18, 0x00, 0x10, 0x14, 0x17, 0x00, 0x11, 0x14, 0x17, 0x00, 0x10, 0x14, +0x18, 0x00, 0x12, 0x15, 0x18, 0x00, 0x10, 0x15, 0x19, 0x00, 0x12, 0x15, 0x19, 0x00, 0x10, 0x15, +0x1a, 0x00, 0x11, 0x15, 0x1a, 0x00, 0x12, 0x15, 0x1a, 0x00, 0x12, 0x15, 0x1b, 0x00, 0x11, 0x16, +0x1a, 0x00, 0x12, 0x16, 0x1a, 0x00, 0x13, 0x16, 0x1a, 0x00, 0x12, 0x16, 0x1b, 0x00, 0x13, 0x16, +0x1b, 0x00, 0x12, 0x17, 0x1b, 0x00, 0x13, 0x17, 0x1b, 0x00, 0x13, 0x17, 0x1c, 0x00, 0x14, 0x17, +0x1c, 0x00, 0x14, 0x17, 0x1d, 0x00, 0x13, 0x18, 0x1d, 0x00, 0x14, 0x18, 0x1d, 0x00, 0x15, 0x18, +0x1f, 0x00, 0x14, 0x19, 0x1d, 0x00, 0x15, 0x19, 0x1d, 0x00, 0x15, 0x19, 0x1e, 0x00, 0x15, 0x19, +0x1f, 0x00, 0x16, 0x19, 0x1f, 0x00, 0x15, 0x19, 0x20, 0x00, 0x16, 0x1a, 0x1e, 0x00, 0x14, 0x1a, +0x1f, 0x00, 0x15, 0x1a, 0x1f, 0x00, 0x16, 0x1a, 0x1f, 0x00, 0x15, 0x1a, 0x20, 0x00, 0x16, 0x1a, +0x21, 0x00, 0x15, 0x1b, 0x21, 0x00, 0x17, 0x1b, 0x22, 0x00, 0x17, 0x1b, 0x23, 0x00, 0x16, 0x1c, +0x22, 0x00, 0x17, 0x1d, 0x22, 0x00, 0x17, 0x1d, 0x23, 0x00, 0x18, 0x1d, 0x23, 0x00, 0x19, 0x1d, +0x23, 0x00, 0x17, 0x1d, 0x24, 0x00, 0x18, 0x1e, 0x23, 0x00, 0x18, 0x1e, 0x24, 0x00, 0x18, 0x1e, +0x25, 0x00, 0x19, 0x1e, 0x25, 0x00, 0x18, 0x1e, 0x26, 0x00, 0x19, 0x1f, 0x26, 0x00, 0x1a, 0x1f, +0x26, 0x00, 0x1a, 0x1f, 0x27, 0x00, 0x1a, 0x1f, 0x28, 0x00, 0x1a, 0x20, 0x26, 0x00, 0x19, 0x20, +0x27, 0x00, 0x1a, 0x20, 0x27, 0x00, 0x1a, 0x20, 0x28, 0x00, 0x1b, 0x20, 0x29, 0x00, 0x1b, 0x21, +0x28, 0x00, 0x1b, 0x21, 0x29, 0x00, 0x1c, 0x21, 0x2a, 0x00, 0x1d, 0x22, 0x29, 0x00, 0x1c, 0x22, +0x2a, 0x00, 0x1d, 0x22, 0x2a, 0x00, 0x1e, 0x23, 0x2c, 0x00, 0x1d, 0x24, 0x2b, 0x00, 0x1d, 0x24, +0x2c, 0x00, 0x1c, 0x24, 0x2d, 0x00, 0x1e, 0x25, 0x2d, 0x00, 0x1f, 0x25, 0x2e, 0x00, 0x1e, 0x26, +0x2e, 0x00, 0x1f, 0x26, 0x2e, 0x00, 0x1f, 0x26, 0x30, 0x00, 0x21, 0x27, 0x2e, 0x00, 0x20, 0x27, +0x2f, 0x00, 0x20, 0x27, 0x31, 0x00, 0x20, 0x29, 0x32, 0x00, 0x20, 0x29, 0x33, 0x00, 0x21, 0x2a, +0x33, 0x00, 0x24, 0x2c, 0x35, 0x00, 0x24, 0x2c, 0x38, 0x00, 0x26, 0x31, 0x3b, 0x00, 0x27, 0x31, +0x3b, 0x00, 0xff, 0x00, 0xff, 0x00, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x05, 0x1a, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x48, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4a, 0x54, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, 0x8c, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x41, 0x5e, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x0d, 0x27, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1b, 0x1f, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x00, 0x32, 0x69, 0x29, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x21, 0x38, 0x63, 0x2b, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3d, 0x2b, 0x1c, 0x0e, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5d, 0x7e, 0x2b, 0x08, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5d, 0x80, 0x4d, 0x4c, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x24, 0x4a, 0x9c, 0xa1, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x10, 0x39, 0x76, 0x46, 0x6d, 0x9a, +0x60, 0x3f, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x01, 0x78, 0x8e, 0x32, 0x37, 0x81, 0x6c, 0x2a, 0x3c, +0x55, 0x2c, 0x1d, 0x2d, 0x17, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x06, 0x27, 0x55, 0x82, 0x9b, 0x45, 0x0e, 0x47, 0x61, 0x0f, 0x1a, +0x0f, 0x20, 0x62, 0x28, 0x0f, 0x18, 0x08, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0x35, 0x1b, 0x40, 0x98, 0x4d, 0x18, 0x15, 0x1c, 0x26, 0x1b, 0x3b, 0x49, +0x20, 0x46, 0x67, 0x1b, 0x13, 0x0c, 0x15, 0x20, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0x25, 0x4d, 0x3c, 0x34, 0x2b, 0x1c, 0x08, 0x0b, 0x0f, 0x1b, 0x32, 0x9e, 0x8a, +0x08, 0x27, 0x22, 0x0f, 0x36, 0x6f, 0x75, 0x3b, 0x39, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0x7d, 0x7a, 0x56, 0x48, 0x68, 0x32, 0x06, 0xa3, 0xa3, 0xa3, 0x14, 0x40, 0x86, 0x6e, +0xa3, 0xa3, 0xa3, 0x06, 0x2d, 0x85, 0x92, 0x5b, 0x32, 0x6a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0x1c, 0x58, 0x8a, 0x65, 0x18, 0x21, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x07, 0x43, 0x83, 0x43, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x73, 0x91, 0x7f, 0x62, 0x20, 0x15, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1e, 0x31, 0x0f, 0x2e, 0x1c, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1b, 0x1b, 0x32, 0x16, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x43, 0x3b, 0x22, 0x55, 0x47, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x72, 0x9d, 0x60, 0x33, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2d, 0x0f, 0x1b, 0x0f, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x11, 0x2d, 0x93, 0x55, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x50, +0x44, 0x90, 0x8a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3a, 0x77, 0x88, 0x47, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x45, 0x25, 0x25, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, +0x51, 0x23, 0x43, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x57, 0x5a, 0xa0, 0x84, 0x87, +0x74, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x02, 0x08, 0x8f, 0x8b, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0x96, +0x6a, 0x41, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4e, 0x95, 0x68, 0x5a, 0x4b, 0x7b, +0x70, 0x4c, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x04, 0x53, 0x8d, 0x79, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x79, 0x80, +0x5b, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x0e, 0x7c, 0x94, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4f, 0x6a, +0x47, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5f, 0x40, 0x1b, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x46, 0x2d, +0x08, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x97, 0x82, 0x18, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x58, 0x43, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x69, 0x5c, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x71, 0x40, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x65, 0x60, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x52, 0x2d, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x66, 0x5e, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2d, 0x20, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x6f, 0x6b, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2b, 0x5b, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2e, 0x44, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x41, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x40, 0x1e, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x10, 0x38, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x69, 0x42, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3b, +0x19, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x03, 0x20, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x53, +0x1d, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4f, 0x15, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1d, 0x09, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x48, 0x89, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1e, 0x33, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5b, 0x39, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0x3b, 0x2a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x59, 0x9f, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0x33, 0x0a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2f, 0x64, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0x1d, 0x2b, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x12, 0x99, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0x49, 0x3e, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1c, 0x36, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x30, 0x5e, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, 0x48, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3 diff --git a/source/quakedef.h b/source/quakedef.h new file mode 100644 index 0000000..5096ec1 --- /dev/null +++ b/source/quakedef.h @@ -0,0 +1,324 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2017 QuakeSpasm developers + +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 QUAKEDEFS_H +#define QUAKEDEFS_H + +// quakedef.h -- primary header for client + +#define QUAKE_GAME // as opposed to utilities + +#define VERSION 1.09 +#define GLQUAKE_VERSION 1.00 +#define D3DQUAKE_VERSION 0.01 +#define WINQUAKE_VERSION 0.996 +#define LINUX_VERSION 1.30 +#define X11_VERSION 1.10 + +#define FITZQUAKE_VERSION 0.85 //johnfitz +#define QUAKESPASM_VERSION 0.93 +#define QUAKESPASM_VER_PATCH 1 // helper to print a string like 0.93.1 +#ifndef QUAKESPASM_VER_SUFFIX +#define QUAKESPASM_VER_SUFFIX // optional version suffix string literal like "-beta1" +#endif + +#define QS_STRINGIFY_(x) #x +#define QS_STRINGIFY(x) QS_STRINGIFY_(x) + +// combined version string like "0.92.1-beta1" +#define QUAKESPASM_VER_STRING QS_STRINGIFY(QUAKESPASM_VERSION) "." QS_STRINGIFY(QUAKESPASM_VER_PATCH) QUAKESPASM_VER_SUFFIX + +//define PARANOID // speed sapping error checking + +#define GAMENAME "id1" // directory to look in by default + +#include "q_stdinc.h" + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +#define CACHE_SIZE 32 // used to align key data structures + +#define Q_UNUSED(x) (x = x) // for pesky compiler / lint warnings + +#define MINIMUM_MEMORY 0x550000 +#define MINIMUM_MEMORY_LEVELPAK (MINIMUM_MEMORY + 0x100000) + +#define MAX_NUM_ARGVS 50 + +// up / down +#define PITCH 0 + +// left / right +#define YAW 1 + +// fall over +#define ROLL 2 + + +#define MAX_QPATH 64 // max length of a quake game pathname + +#define ON_EPSILON 0.1 // point on plane side epsilon + +#define DIST_EPSILON (0.03125) // 1/32 epsilon to keep floating point happy (moved from world.c) + +#define MAX_MSGLEN 64000 // max length of a reliable message //ericw -- was 32000 +#define MAX_DATAGRAM 32000 // max length of unreliable message //johnfitz -- was 1024 + +#define DATAGRAM_MTU 1400 // johnfitz -- actual limit for unreliable messages to nonlocal clients + +// +// per-level limits +// +#define MIN_EDICTS 256 // johnfitz -- lowest allowed value for max_edicts cvar +#define MAX_EDICTS 32000 // johnfitz -- highest allowed value for max_edicts cvar + // ents past 8192 can't play sounds in the standard protocol +#define MAX_LIGHTSTYLES 64 +#define MAX_MODELS 2048 // johnfitz -- was 256 +#define MAX_SOUNDS 2048 // johnfitz -- was 256 + +#define SAVEGAME_COMMENT_LENGTH 39 + +#define MAX_STYLESTRING 64 + +// +// stats are integers communicated to the client by the server +// +#define MAX_CL_STATS 32 +#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 + +// 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) + +//=========================================== +//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< +#include +#ifndef APIENTRY +#define APIENTRY +#endif + +#include "console.h" +#include "wad.h" +#include "vid.h" +#include "screen.h" +#include "draw.h" +#include "render.h" +#include "view.h" +#include "sbar.h" +#include "q_sound.h" +#include "client.h" + +#include "gl_model.h" +#include "world.h" + +#include "image.h" //johnfitz +#include "gl_texmgr.h" //johnfitz +#include "input.h" +#include "keys.h" +#include "menu.h" +#include "cdaudio.h" +#include "glquake.h" + + +//============================================================================= + +// the host system specifies the base of the directory tree, the +// command line parms passed to the program, and the amount of memory +// available for the program to use + +extern qboolean noclip_anglehack; + +// +// host +// +extern quakeparms_t *host_parms; + +extern cvar_t sys_ticrate; +extern cvar_t sys_throttle; +extern cvar_t sys_nostdout; +extern cvar_t developer; +extern cvar_t max_edicts; //johnfitz + +extern qboolean host_initialized; // true if into command execution +extern double host_frametime; +extern byte *host_colormap; +extern int host_framecount; // incremented every frame, never reset +extern double realtime; // not bounded in any way, changed at + // start of every frame, never reset + +typedef struct filelist_item_s +{ + char name[32]; + struct filelist_item_s *next; +} filelist_item_t; + +extern filelist_item_t *modlist; +extern filelist_item_t *extralevels; +extern filelist_item_t *demolist; + +void Host_ClearMemory (void); +void Host_ServerFrame (void); +void Host_InitCommands (void); +void Host_Init (void); +void Host_Shutdown(void); +void Host_Callback_Notify (cvar_t *var); /* callback function for CVAR_NOTIFY */ +FUNC_NORETURN void Host_Error (const char *error, ...) FUNC_PRINTF(1,2); +FUNC_NORETURN void Host_EndGame (const char *message, ...) FUNC_PRINTF(1,2); +#ifdef __WATCOMC__ +#pragma aux Host_Error aborts; +#pragma aux Host_EndGame aborts; +#endif +void Host_Frame (float time); +void Host_Quit_f (void); +void Host_ClientCommands (const char *fmt, ...) FUNC_PRINTF(1,2); +void Host_ShutdownServer (qboolean crash); +void Host_WriteConfiguration (void); + +void ExtraMaps_Init (void); +void Modlist_Init (void); +void DemoList_Init (void); + +void DemoList_Rebuild (void); + +extern int current_skill; // skill level for currently loaded level (in case + // the user changes the cvar while the level is + // running, this reflects the level actually in use) + +extern qboolean isDedicated; + +extern int minimum_memory; + +#endif /* QUAKEDEFS_H */ + diff --git a/source/r_alias.c b/source/r_alias.c new file mode 100644 index 0000000..1f1f1e1 --- /dev/null +++ b/source/r_alias.c @@ -0,0 +1,992 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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_alias.c -- alias model rendering + +#include "quakedef.h" + +extern cvar_t r_drawflat, gl_overbright_models, gl_fullbrights, r_lerpmodels, r_lerpmove; //johnfitz + +//up to 16 color translated skins +gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz -- changed to an array of pointers + +#define NUMVERTEXNORMALS 162 + +float r_avertexnormals[NUMVERTEXNORMALS][3] = +{ +#include "anorms.h" +}; + +extern vec3_t lightcolor; //johnfitz -- replaces "float shadelight" for lit support + +// precalculated dot products for quantized angles +#define SHADEDOT_QUANT 16 +float r_avertexnormal_dots[SHADEDOT_QUANT][256] = +{ +#include "anorm_dots.h" +}; + +extern vec3_t lightspot; + +float *shadedots = r_avertexnormal_dots[0]; +vec3_t shadevector; + +float entalpha; //johnfitz + +qboolean overbright; //johnfitz + +qboolean shading = true; //johnfitz -- if false, disable vertex shading for various reasons (fullbright, r_lightmap, showtris, etc) + +//johnfitz -- struct for passing lerp information to drawing functions +typedef struct { + short pose1; + short pose2; + float blend; + vec3_t origin; + vec3_t angles; +} lerpdata_t; +//johnfitz + +static GLuint r_alias_program; + +// uniforms used in vert shader +static GLuint blendLoc; +static GLuint shadevectorLoc; +static GLuint lightColorLoc; + +// uniforms used in frag shader +static GLuint texLoc; +static GLuint fullbrightTexLoc; +static GLuint useFullbrightTexLoc; +static GLuint useOverbrightLoc; +static GLuint useAlphaTestLoc; + +#define pose1VertexAttrIndex 0 +#define pose1NormalAttrIndex 1 +#define pose2VertexAttrIndex 2 +#define pose2NormalAttrIndex 3 +#define texCoordsAttrIndex 4 + +/* +============= +GLARB_GetXYZOffset + +Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given +model and pose. +============= +*/ +static void *GLARB_GetXYZOffset (aliashdr_t *hdr, int pose) +{ + const int xyzoffs = offsetof (meshxyz_t, xyz); + return (void *) (currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs); +} + +/* +============= +GLARB_GetNormalOffset + +Returns the offset of the first vertex's meshxyz_t.normal in the vbo for the +given model and pose. +============= +*/ +static void *GLARB_GetNormalOffset (aliashdr_t *hdr, int pose) +{ + const int normaloffs = offsetof (meshxyz_t, normal); + return (void *)(currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + normaloffs); +} + +/* +============= +GLAlias_CreateShaders +============= +*/ +void GLAlias_CreateShaders (void) +{ + const glsl_attrib_binding_t bindings[] = { + { "TexCoords", texCoordsAttrIndex }, + { "Pose1Vert", pose1VertexAttrIndex }, + { "Pose1Normal", pose1NormalAttrIndex }, + { "Pose2Vert", pose2VertexAttrIndex }, + { "Pose2Normal", pose2NormalAttrIndex } + }; + + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "uniform float Blend;\n" + "uniform vec3 ShadeVector;\n" + "uniform vec4 LightColor;\n" + "attribute vec4 TexCoords; // only xy are used \n" + "attribute vec4 Pose1Vert;\n" + "attribute vec3 Pose1Normal;\n" + "attribute vec4 Pose2Vert;\n" + "attribute vec3 Pose2Normal;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "float r_avertexnormal_dot(vec3 vertexnormal) // from MH \n" + "{\n" + " float dot = dot(vertexnormal, ShadeVector);\n" + " // wtf - this reproduces anorm_dots within as reasonable a degree of tolerance as the >= 0 case\n" + " if (dot < 0.0)\n" + " return 1.0 + dot * (13.0 / 44.0);\n" + " else\n" + " return 1.0 + dot;\n" + "}\n" + "void main()\n" + "{\n" + " gl_TexCoord[0] = TexCoords;\n" + " vec4 lerpedVert = mix(vec4(Pose1Vert.xyz, 1.0), vec4(Pose2Vert.xyz, 1.0), Blend);\n" + " gl_Position = gl_ModelViewProjectionMatrix * lerpedVert;\n" + " FogFragCoord = gl_Position.w;\n" + " float dot1 = r_avertexnormal_dot(Pose1Normal);\n" + " float dot2 = r_avertexnormal_dot(Pose2Normal);\n" + " gl_FrontColor = LightColor * vec4(vec3(mix(dot1, dot2, Blend)), 1.0);\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D Tex;\n" + "uniform sampler2D FullbrightTex;\n" + "uniform bool UseFullbrightTex;\n" + "uniform bool UseOverbright;\n" + "uniform bool UseAlphaTest;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " vec4 result = texture2D(Tex, gl_TexCoord[0].xy);\n" + " if (UseAlphaTest && (result.a < 0.666))\n" + " discard;\n" + " result *= gl_Color;\n" + " if (UseOverbright)\n" + " result.rgb *= 2.0;\n" + " if (UseFullbrightTex)\n" + " result += texture2D(FullbrightTex, gl_TexCoord[0].xy);\n" + " result = clamp(result, 0.0, 1.0);\n" + " float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n" + " fog = clamp(fog, 0.0, 1.0);\n" + " result = mix(gl_Fog.color, result, fog);\n" + " result.a = gl_Color.a;\n" // FIXME: This will make almost transparent things cut holes though heavy fog + " gl_FragColor = result;\n" + "}\n"; + + if (!gl_glsl_alias_able) + return; + + r_alias_program = GL_CreateProgram (vertSource, fragSource, sizeof(bindings)/sizeof(bindings[0]), bindings); + + if (r_alias_program != 0) + { + // get uniform locations + blendLoc = GL_GetUniformLocation (&r_alias_program, "Blend"); + shadevectorLoc = GL_GetUniformLocation (&r_alias_program, "ShadeVector"); + lightColorLoc = GL_GetUniformLocation (&r_alias_program, "LightColor"); + texLoc = GL_GetUniformLocation (&r_alias_program, "Tex"); + fullbrightTexLoc = GL_GetUniformLocation (&r_alias_program, "FullbrightTex"); + useFullbrightTexLoc = GL_GetUniformLocation (&r_alias_program, "UseFullbrightTex"); + useOverbrightLoc = GL_GetUniformLocation (&r_alias_program, "UseOverbright"); + useAlphaTestLoc = GL_GetUniformLocation (&r_alias_program, "UseAlphaTest"); + } +} + +/* +============= +GL_DrawAliasFrame_GLSL -- ericw + +Optimized alias model drawing codepath. +Compared to the original GL_DrawAliasFrame, this makes 1 draw call, +no vertex data is uploaded (it's already in the r_meshvbo and r_meshindexesvbo +static VBOs), and lerping and lighting is done in the vertex shader. + +Supports optional overbright, optional fullbright pixels. + +Based on code by MH from RMQEngine +============= +*/ +void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltexture_t *tx, gltexture_t *fb) +{ + float blend; + + if (lerpdata.pose1 != lerpdata.pose2) + { + blend = lerpdata.blend; + } + else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled + { + blend = 0; + } + + GL_UseProgramFunc (r_alias_program); + + GL_BindBuffer (GL_ARRAY_BUFFER, currententity->model->meshvbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, currententity->model->meshindexesvbo); + + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, 0, (void *)(intptr_t)currententity->model->vbostofs); + GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2)); +// GL_TRUE to normalize the signed bytes to [-1 .. 1] + GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose2)); + +// set uniforms + GL_Uniform1fFunc (blendLoc, blend); + GL_Uniform3fFunc (shadevectorLoc, shadevector[0], shadevector[1], shadevector[2]); + GL_Uniform4fFunc (lightColorLoc, lightcolor[0], lightcolor[1], lightcolor[2], entalpha); + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (fullbrightTexLoc, 1); + GL_Uniform1iFunc (useFullbrightTexLoc, (fb != NULL) ? 1 : 0); + GL_Uniform1fFunc (useOverbrightLoc, overbright ? 1 : 0); + GL_Uniform1iFunc (useAlphaTestLoc, (currententity->model->flags & MF_HOLEY) ? 1 : 0); + +// set textures + GL_SelectTexture (GL_TEXTURE0); + GL_Bind (tx); + + if (fb) + { + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (fb); + } + +// draw + glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (void *)(intptr_t)currententity->model->vboindexofs); + +// clean up + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + + rs_aliaspasses += paliashdr->numtris; +} + +/* +============= +GL_DrawAliasFrame -- johnfitz -- rewritten to support colored light, lerping, entalpha, multitexture, and r_drawflat +============= +*/ +void GL_DrawAliasFrame (aliashdr_t *paliashdr, lerpdata_t lerpdata) +{ + float vertcolor[4]; + trivertx_t *verts1, *verts2; + int *commands; + int count; + float u,v; + float blend, iblend; + qboolean lerping; + + if (lerpdata.pose1 != lerpdata.pose2) + { + lerping = true; + verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); + verts2 = verts1; + verts1 += lerpdata.pose1 * paliashdr->poseverts; + verts2 += lerpdata.pose2 * paliashdr->poseverts; + blend = lerpdata.blend; + iblend = 1.0f - blend; + } + else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled + { + lerping = false; + verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); + verts2 = verts1; // avoid bogus compiler warning + verts1 += lerpdata.pose1 * paliashdr->poseverts; + blend = iblend = 0; // avoid bogus compiler warning + } + + commands = (int *)((byte *)paliashdr + paliashdr->commands); + + vertcolor[3] = entalpha; //never changes, so there's no need to put this inside the loop + + while (1) + { + // get the vertex count and primitive type + count = *commands++; + if (!count) + break; // done + + if (count < 0) + { + count = -count; + glBegin (GL_TRIANGLE_FAN); + } + else + glBegin (GL_TRIANGLE_STRIP); + + do + { + u = ((float *)commands)[0]; + v = ((float *)commands)[1]; + if (mtexenabled) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, u, v); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, u, v); + } + else + glTexCoord2f (u, v); + + commands += 2; + + if (shading) + { + if (r_drawflat_cheatsafe) + { + srand(count * (unsigned int)(src_offset_t)commands); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + } + else if (lerping) + { + vertcolor[0] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[0]; + vertcolor[1] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[1]; + vertcolor[2] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[2]; + glColor4fv (vertcolor); + } + else + { + vertcolor[0] = shadedots[verts1->lightnormalindex] * lightcolor[0]; + vertcolor[1] = shadedots[verts1->lightnormalindex] * lightcolor[1]; + vertcolor[2] = shadedots[verts1->lightnormalindex] * lightcolor[2]; + glColor4fv (vertcolor); + } + } + + if (lerping) + { + glVertex3f (verts1->v[0]*iblend + verts2->v[0]*blend, + verts1->v[1]*iblend + verts2->v[1]*blend, + verts1->v[2]*iblend + verts2->v[2]*blend); + verts1++; + verts2++; + } + else + { + glVertex3f (verts1->v[0], verts1->v[1], verts1->v[2]); + verts1++; + } + } while (--count); + + glEnd (); + } + + rs_aliaspasses += paliashdr->numtris; +} + +/* +================= +R_SetupAliasFrame -- johnfitz -- rewritten to support lerping +================= +*/ +void R_SetupAliasFrame (aliashdr_t *paliashdr, int frame, lerpdata_t *lerpdata) +{ + entity_t *e = currententity; + int posenum, numposes; + + if ((frame >= paliashdr->numframes) || (frame < 0)) + { + Con_DPrintf ("R_AliasSetupFrame: no such frame %d for '%s'\n", frame, e->model->name); + frame = 0; + } + + posenum = paliashdr->frames[frame].firstpose; + numposes = paliashdr->frames[frame].numposes; + + if (numposes > 1) + { + e->lerptime = paliashdr->frames[frame].interval; + posenum += (int)(cl.time / e->lerptime) % numposes; + } + else + e->lerptime = 0.1; + + if (e->lerpflags & LERP_RESETANIM) //kill any lerp in progress + { + e->lerpstart = 0; + e->previouspose = posenum; + e->currentpose = posenum; + e->lerpflags -= LERP_RESETANIM; + } + else if (e->currentpose != posenum) // pose changed, start new lerp + { + if (e->lerpflags & LERP_RESETANIM2) //defer lerping one more time + { + e->lerpstart = 0; + e->previouspose = posenum; + e->currentpose = posenum; + e->lerpflags -= LERP_RESETANIM2; + } + else + { + e->lerpstart = cl.time; + e->previouspose = e->currentpose; + e->currentpose = posenum; + } + } + + //set up values + if (r_lerpmodels.value && !(e->model->flags & MOD_NOLERP && r_lerpmodels.value != 2)) + { + if (e->lerpflags & LERP_FINISH && numposes == 1) + lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / (e->lerpfinish - e->lerpstart), 1); + else + lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / e->lerptime, 1); + lerpdata->pose1 = e->previouspose; + lerpdata->pose2 = e->currentpose; + } + else //don't lerp + { + lerpdata->blend = 1; + lerpdata->pose1 = posenum; + lerpdata->pose2 = posenum; + } +} + +/* +================= +R_SetupEntityTransform -- johnfitz -- set up transform part of lerpdata +================= +*/ +void R_SetupEntityTransform (entity_t *e, lerpdata_t *lerpdata) +{ + float blend; + vec3_t d; + int i; + + // if LERP_RESETMOVE, kill any lerps in progress + if (e->lerpflags & LERP_RESETMOVE) + { + e->movelerpstart = 0; + VectorCopy (e->origin, e->previousorigin); + VectorCopy (e->origin, e->currentorigin); + VectorCopy (e->angles, e->previousangles); + VectorCopy (e->angles, e->currentangles); + e->lerpflags -= LERP_RESETMOVE; + } + else if (!VectorCompare (e->origin, e->currentorigin) || !VectorCompare (e->angles, e->currentangles)) // origin/angles changed, start new lerp + { + e->movelerpstart = cl.time; + VectorCopy (e->currentorigin, e->previousorigin); + VectorCopy (e->origin, e->currentorigin); + VectorCopy (e->currentangles, e->previousangles); + VectorCopy (e->angles, e->currentangles); + } + + //set up values + if (r_lerpmove.value && e != &cl.viewent && e->lerpflags & LERP_MOVESTEP) + { + if (e->lerpflags & LERP_FINISH) + blend = CLAMP (0, (cl.time - e->movelerpstart) / (e->lerpfinish - e->movelerpstart), 1); + else + blend = CLAMP (0, (cl.time - e->movelerpstart) / 0.1, 1); + + //translation + VectorSubtract (e->currentorigin, e->previousorigin, d); + lerpdata->origin[0] = e->previousorigin[0] + d[0] * blend; + lerpdata->origin[1] = e->previousorigin[1] + d[1] * blend; + lerpdata->origin[2] = e->previousorigin[2] + d[2] * blend; + + //rotation + VectorSubtract (e->currentangles, e->previousangles, d); + for (i = 0; i < 3; i++) + { + if (d[i] > 180) d[i] -= 360; + if (d[i] < -180) d[i] += 360; + } + lerpdata->angles[0] = e->previousangles[0] + d[0] * blend; + lerpdata->angles[1] = e->previousangles[1] + d[1] * blend; + lerpdata->angles[2] = e->previousangles[2] + d[2] * blend; + } + else //don't lerp + { + VectorCopy (e->origin, lerpdata->origin); + VectorCopy (e->angles, lerpdata->angles); + } +} + +/* +================= +R_SetupAliasLighting -- johnfitz -- broken out from R_DrawAliasModel and rewritten +================= +*/ +void R_SetupAliasLighting (entity_t *e) +{ + vec3_t dist; + float add; + int i; + int quantizedangle; + float radiansangle; + + R_LightPoint (e->origin); + + //add dlights + for (i=0 ; i= cl.time) + { + VectorSubtract (currententity->origin, cl_dlights[i].origin, dist); + add = cl_dlights[i].radius - VectorLength(dist); + if (add > 0) + VectorMA (lightcolor, add, cl_dlights[i].color, lightcolor); + } + } + + // minimum light value on gun (24) + if (e == &cl.viewent) + { + add = 72.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add > 0.0f) + { + lightcolor[0] += add / 3.0f; + lightcolor[1] += add / 3.0f; + lightcolor[2] += add / 3.0f; + } + } + + // minimum light value on players (8) + if (currententity > cl_entities && currententity <= cl_entities + cl.maxclients) + { + add = 24.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add > 0.0f) + { + lightcolor[0] += add / 3.0f; + lightcolor[1] += add / 3.0f; + lightcolor[2] += add / 3.0f; + } + } + + // clamp lighting so it doesn't overbright as much (96) + if (overbright) + { + add = 288.0f / (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add < 1.0f) + VectorScale(lightcolor, add, lightcolor); + } + + //hack up the brightness when fullbrights but no overbrights (256) + if (gl_fullbrights.value && !gl_overbright_models.value) + if (e->model->flags & MOD_FBRIGHTHACK) + { + lightcolor[0] = 256.0f; + lightcolor[1] = 256.0f; + lightcolor[2] = 256.0f; + } + + quantizedangle = ((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1); + +//ericw -- shadevector is passed to the shader to compute shadedots inside the +//shader, see GLAlias_CreateShaders() + radiansangle = (quantizedangle / 16.0) * 2.0 * 3.14159; + shadevector[0] = cos(-radiansangle); + shadevector[1] = sin(-radiansangle); + shadevector[2] = 1; + VectorNormalize(shadevector); +//ericw -- + + shadedots = r_avertexnormal_dots[quantizedangle]; + VectorScale (lightcolor, 1.0f / 200.0f, lightcolor); +} + +/* +================= +R_DrawAliasModel -- johnfitz -- almost completely rewritten +================= +*/ +void R_DrawAliasModel (entity_t *e) +{ + aliashdr_t *paliashdr; + int i, anim, skinnum; + gltexture_t *tx, *fb; + lerpdata_t lerpdata; + qboolean alphatest = !!(e->model->flags & MF_HOLEY); + + // + // setup pose/lerp data -- do it first so we don't miss updates due to culling + // + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + + // + // cull it + // + if (R_CullModelForEntity(e)) + return; + + // + // transform it + // + glPushMatrix (); + R_RotateForEntity (lerpdata.origin, lerpdata.angles); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); + glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); + + // + // random stuff + // + if (gl_smoothmodels.value && !r_drawflat_cheatsafe) + glShadeModel (GL_SMOOTH); + if (gl_affinemodels.value) + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + overbright = gl_overbright_models.value; + shading = true; + + // + // set up for alpha blending + // + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) //no alpha in drawflat or lightmap mode + entalpha = 1; + else + entalpha = ENTALPHA_DECODE(e->alpha); + if (entalpha == 0) + goto cleanup; + if (entalpha < 1) + { + if (!gl_texture_env_combine) overbright = false; //overbright can't be done in a single pass without combiners + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + } + else if (alphatest) + glEnable (GL_ALPHA_TEST); + + // + // set up lighting + // + rs_aliaspolys += paliashdr->numtris; + R_SetupAliasLighting (e); + + // + // set up textures + // + GL_DisableMultitexture(); + anim = (int)(cl.time*10) & 3; + skinnum = e->skinnum; + if ((skinnum >= paliashdr->numskins) || (skinnum < 0)) + { + Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name); + // ericw -- display skin 0 for winquake compatibility + skinnum = 0; + } + tx = paliashdr->gltextures[skinnum][anim]; + fb = paliashdr->fbtextures[skinnum][anim]; + if (e->colormap != vid.colormap && !gl_nocolors.value) + { + i = e - cl_entities; + if (i >= 1 && i<=cl.maxclients /* && !strcmp (currententity->model->name, "progs/player.mdl") */) + tx = playertextures[i - 1]; + } + if (!gl_fullbrights.value) + fb = NULL; + + // + // draw it + // + if (r_drawflat_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + srand((int) (cl.time * 1000)); //restore randomness + } + else if (r_fullbright_cheatsafe) + { + GL_Bind (tx); + shading = false; + glColor4f(1,1,1,entalpha); + GL_DrawAliasFrame (paliashdr, lerpdata); + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + } + } + else if (r_lightmap_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + shading = false; + glColor3f(1,1,1); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + } +// call fast path if possible. if the shader compliation failed for some reason, +// r_alias_program will be 0. + else if (r_alias_program != 0) + { + GL_DrawAliasFrame_GLSL (paliashdr, lerpdata, tx, fb); + } + else if (overbright) + { + if (gl_texture_env_combine && gl_mtexable && gl_texture_env_add && fb) //case 1: everything in one pass + { + GL_Bind (tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); + glDisable(GL_BLEND); + GL_DisableMultitexture(); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (gl_texture_env_combine) //case 2: overbright in one pass, then fullbright pass + { + // first pass + GL_Bind(tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_DrawAliasFrame (paliashdr, lerpdata); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + // second pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + else //case 3: overbright in two passes, then fullbright pass + { + // first pass + GL_Bind(tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DrawAliasFrame (paliashdr, lerpdata); + // second pass -- additive with black fog, to double the object colors but not the fog color + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + // third pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + } + else + { + if (gl_mtexable && gl_texture_env_add && fb) //case 4: fullbright mask using multitexture + { + GL_DisableMultitexture(); // selects TEXTURE0 + GL_Bind (tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); + glDisable(GL_BLEND); + GL_DisableMultitexture(); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else //case 5: fullbright mask without multitexture + { + // first pass + GL_Bind(tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DrawAliasFrame (paliashdr, lerpdata); + // second pass + if (fb) + { + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + } + } + } + +cleanup: + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + glShadeModel (GL_FLAT); + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + if (alphatest) + glDisable (GL_ALPHA_TEST); + glColor3f(1,1,1); + glPopMatrix (); +} + +//johnfitz -- values for shadow matrix +#define SHADOW_SKEW_X -0.7 //skew along x axis. -0.7 to mimic glquake shadows +#define SHADOW_SKEW_Y 0 //skew along y axis. 0 to mimic glquake shadows +#define SHADOW_VSCALE 0 //0=completely flat +#define SHADOW_HEIGHT 0.1 //how far above the floor to render the shadow +//johnfitz + +/* +============= +GL_DrawAliasShadow -- johnfitz -- rewritten + +TODO: orient shadow onto "lightplane" (a global mplane_t*) +============= +*/ +void GL_DrawAliasShadow (entity_t *e) +{ + float shadowmatrix[16] = {1, 0, 0, 0, + 0, 1, 0, 0, + SHADOW_SKEW_X, SHADOW_SKEW_Y, SHADOW_VSCALE, 0, + 0, 0, SHADOW_HEIGHT, 1}; + float lheight; + aliashdr_t *paliashdr; + lerpdata_t lerpdata; + + if (R_CullModelForEntity(e)) + return; + + if (e == &cl.viewent || e->model->flags & MOD_NOSHADOW) + return; + + entalpha = ENTALPHA_DECODE(e->alpha); + if (entalpha == 0) return; + + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + R_LightPoint (e->origin); + lheight = currententity->origin[2] - lightspot[2]; + +// set up matrix + glPushMatrix (); + glTranslatef (lerpdata.origin[0], lerpdata.origin[1], lerpdata.origin[2]); + glTranslatef (0,0,-lheight); + glMultMatrixf (shadowmatrix); + glTranslatef (0,0,lheight); + glRotatef (lerpdata.angles[1], 0, 0, 1); + glRotatef (-lerpdata.angles[0], 0, 1, 0); + glRotatef (lerpdata.angles[2], 1, 0, 0); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); + glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); + +// draw it + glDepthMask(GL_FALSE); + glEnable (GL_BLEND); + GL_DisableMultitexture (); + glDisable (GL_TEXTURE_2D); + shading = false; + glColor4f(0,0,0,entalpha * 0.5); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + glDepthMask(GL_TRUE); + +//clean up + glPopMatrix (); +} + +/* +================= +R_DrawAliasModel_ShowTris -- johnfitz +================= +*/ +void R_DrawAliasModel_ShowTris (entity_t *e) +{ + aliashdr_t *paliashdr; + lerpdata_t lerpdata; + + if (R_CullModelForEntity(e)) + return; + + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + + glPushMatrix (); + R_RotateForEntity (lerpdata.origin,lerpdata.angles); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); + glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); + + shading = false; + glColor3f(1,1,1); + GL_DrawAliasFrame (paliashdr, lerpdata); + + glPopMatrix (); +} + diff --git a/source/r_brush.c b/source/r_brush.c new file mode 100644 index 0000000..7155e5e --- /dev/null +++ b/source/r_brush.c @@ -0,0 +1,1327 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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_brush.c: brush model rendering. renamed from r_surf.c + +#include "quakedef.h" + +extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater; //johnfitz +extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix + +int gl_lightmap_format; +int lightmap_bytes; + +#define BLOCK_WIDTH 128 +#define BLOCK_HEIGHT 128 + +gltexture_t *lightmap_textures[MAX_LIGHTMAPS]; //johnfitz -- changed to an array + +unsigned blocklights[BLOCK_WIDTH*BLOCK_HEIGHT*3]; //johnfitz -- was 18*18, added lit support (*3) and loosened surface extents maximum (BLOCK_WIDTH*BLOCK_HEIGHT) + +typedef struct glRect_s { + unsigned char l,t,w,h; +} glRect_t; + +glpoly_t *lightmap_polys[MAX_LIGHTMAPS]; +qboolean lightmap_modified[MAX_LIGHTMAPS]; +glRect_t lightmap_rectchange[MAX_LIGHTMAPS]; + +int allocated[MAX_LIGHTMAPS][BLOCK_WIDTH]; +int last_lightmap_allocated; //ericw -- optimization: remember the index of the last lightmap AllocBlock stored a surf in + +// the lightmap texture data needs to be kept in +// main memory so texsubimage can update properly +byte lightmaps[4*MAX_LIGHTMAPS*BLOCK_WIDTH*BLOCK_HEIGHT]; + + +/* +=============== +R_TextureAnimation -- johnfitz -- added "frame" param to eliminate use of "currententity" global + +Returns the proper texture for a given time and base texture +=============== +*/ +texture_t *R_TextureAnimation (texture_t *base, int frame) +{ + int relative; + int count; + + if (frame) + if (base->alternate_anims) + base = base->alternate_anims; + + if (!base->anim_total) + return base; + + relative = (int)(cl.time*10) % base->anim_total; + + count = 0; + while (base->anim_min > relative || base->anim_max <= relative) + { + base = base->anim_next; + if (!base) + Sys_Error ("R_TextureAnimation: broken cycle"); + if (++count > 100) + Sys_Error ("R_TextureAnimation: infinite cycle"); + } + + return base; +} + +/* +================ +DrawGLPoly +================ +*/ +void DrawGLPoly (glpoly_t *p) +{ + float *v; + int i; + + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (v[3], v[4]); + glVertex3fv (v); + } + glEnd (); +} + +/* +================ +DrawGLTriangleFan -- johnfitz -- like DrawGLPoly but for r_showtris +================ +*/ +void DrawGLTriangleFan (glpoly_t *p) +{ + float *v; + int i; + + glBegin (GL_TRIANGLE_FAN); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glVertex3fv (v); + } + glEnd (); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +#if 0 +/* +================ +R_DrawSequentialPoly -- johnfitz -- rewritten +================ +*/ +void R_DrawSequentialPoly (msurface_t *s) +{ + glpoly_t *p; + texture_t *t; + float *v; + float entalpha; + int i; + + t = R_TextureAnimation (s->texinfo->texture, currententity->frame); + entalpha = ENTALPHA_DECODE(currententity->alpha); + +// drawflat + if (r_drawflat_cheatsafe) + { + if ((s->flags & SURF_DRAWTURB) && r_oldwater.value) + { + for (p = s->polys->next; p; p = p->next) + { + srand((unsigned int) (uintptr_t) p); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (p); + rs_brushpasses++; + } + return; + } + + srand((unsigned int) (uintptr_t) s->polys); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (s->polys); + rs_brushpasses++; + return; + } + +// fullbright + if ((r_fullbright_cheatsafe) && !(s->flags & SURF_DRAWTILED)) + { + if (entalpha < 1) + { + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f(1, 1, 1, entalpha); + } + + if (s->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip on alpha test + + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + rs_brushpasses++; + + if (s->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + + if (entalpha < 1) + { + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1, 1, 1); + } + goto fullbrights; + } + +// r_lightmap + if (r_lightmap_cheatsafe) + { + if (s->flags & SURF_DRAWTILED) + { + glDisable (GL_TEXTURE_2D); + DrawGLPoly (s->polys); + glEnable (GL_TEXTURE_2D); + rs_brushpasses++; + return; + } + + R_RenderDynamicLightmaps (s); + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + if (!gl_overbright.value) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0.5, 0.5, 0.5); + } + glBegin (GL_POLYGON); + v = s->polys->verts[0]; + for (i=0 ; ipolys->numverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + if (!gl_overbright.value) + { + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + rs_brushpasses++; + return; + } + +// sky poly -- skip it, already handled in gl_sky.c + if (s->flags & SURF_DRAWSKY) + return; + +// water poly + if (s->flags & SURF_DRAWTURB) + { + if (currententity->alpha == ENTALPHA_DEFAULT) + entalpha = CLAMP(0.0, GL_WaterAlphaForSurface(s), 1.0); + + if (entalpha < 1) + { + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f(1, 1, 1, entalpha); + } + if (r_oldwater.value) + { + GL_Bind (s->texinfo->texture->gltexture); + for (p = s->polys->next; p; p = p->next) + { + DrawWaterPoly (p); + rs_brushpasses++; + } + rs_brushpasses++; + } + else + { + GL_Bind (s->texinfo->texture->warpimage); + s->texinfo->texture->update_warp = true; // FIXME: one frame too late! + DrawGLPoly (s->polys); + rs_brushpasses++; + } + if (entalpha < 1) + { + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1, 1, 1); + } + return; + } + +// missing texture + if (s->flags & SURF_NOTEXTURE) + { + if (entalpha < 1) + { + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f(1, 1, 1, entalpha); + } + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + rs_brushpasses++; + if (entalpha < 1) + { + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1, 1, 1); + } + return; + } + +// lightmapped poly + if (entalpha < 1) + { + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f(1, 1, 1, entalpha); + } + else + glColor3f(1, 1, 1); + + if (s->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip on alpha test + + if (gl_overbright.value) + { + if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners + { + GL_DisableMultitexture(); // selects TEXTURE0 + GL_Bind (t->gltexture); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + R_RenderDynamicLightmaps (s); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + glBegin(GL_POLYGON); + v = s->polys->verts[0]; + for (i=0 ; ipolys->numverts ; i++, v+= VERTEXSIZE) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DisableMultitexture (); + rs_brushpasses++; + } + else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 2: can't do multipass if entity has alpha, so just draw the texture + { + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + rs_brushpasses++; + } + else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass + { + //first pass -- texture with no fog + Fog_DisableGFog (); + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + Fog_EnableGFog (); + rs_brushpasses++; + + //second pass -- lightmap with black fog, modulate blended + R_RenderDynamicLightmaps (s); + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); //2x modulate + Fog_StartAdditive (); + glBegin (GL_POLYGON); + v = s->polys->verts[0]; + for (i=0 ; ipolys->numverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + Fog_StopAdditive (); + rs_brushpasses++; + + //third pass -- black geo with normal fog, additive blended + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + DrawGLPoly (s->polys); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + rs_brushpasses++; + } + + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } + } + else + { + if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation + { + GL_DisableMultitexture(); // selects TEXTURE0 + GL_Bind (t->gltexture); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + R_RenderDynamicLightmaps (s); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBegin(GL_POLYGON); + v = s->polys->verts[0]; + for (i=0 ; ipolys->numverts ; i++, v+= VERTEXSIZE) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + GL_DisableMultitexture (); + rs_brushpasses++; + } + else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 5: can't do multipass if entity has alpha, so just draw the texture + { + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + rs_brushpasses++; + } + else //case 6: texture in one pass, lightmap in a second pass, fog in third pass + { + //first pass -- texture with no fog + Fog_DisableGFog (); + GL_Bind (t->gltexture); + DrawGLPoly (s->polys); + Fog_EnableGFog (); + rs_brushpasses++; + + //second pass -- lightmap with black fog, modulate blended + R_RenderDynamicLightmaps (s); + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_ZERO, GL_SRC_COLOR); //modulate + Fog_StartAdditive (); + glBegin (GL_POLYGON); + v = s->polys->verts[0]; + for (i=0 ; ipolys->numverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + Fog_StopAdditive (); + rs_brushpasses++; + + //third pass -- black geo with normal fog, additive blended + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + DrawGLPoly (s->polys); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + rs_brushpasses++; + } + + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + + } + } + if (entalpha < 1) + { + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1, 1, 1); + } + + if (s->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + +fullbrights: + if (gl_fullbrights.value && t->fullbright) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f (entalpha, entalpha, entalpha); + GL_Bind (t->fullbright); + Fog_StartAdditive (); + DrawGLPoly (s->polys); + Fog_StopAdditive (); + glColor3f(1, 1, 1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + rs_brushpasses++; + } +} +#endif +/* +================= +R_DrawBrushModel +================= +*/ +void R_DrawBrushModel (entity_t *e) +{ + int i, k; + msurface_t *psurf; + float dot; + mplane_t *pplane; + qmodel_t *clmodel; + + if (R_CullModelForEntity(e)) + return; + + currententity = e; + clmodel = e->model; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + vec3_t temp; + vec3_t forward, right, up; + + VectorCopy (modelorg, temp); + AngleVectors (e->angles, forward, right, up); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + +// calculate dynamic lighting for bmodel if it's not an +// instanced model + if (clmodel->firstmodelsurface != 0 && !gl_flashblend.value) + { + for (k=0 ; knodes + clmodel->hulls[0].firstclipnode); + } + } + + glPushMatrix (); + e->angles[0] = -e->angles[0]; // stupid quake bug + if (gl_zfix.value) + { + e->origin[0] -= DIST_EPSILON; + e->origin[1] -= DIST_EPSILON; + e->origin[2] -= DIST_EPSILON; + } + R_RotateForEntity (e->origin, e->angles); + if (gl_zfix.value) + { + e->origin[0] += DIST_EPSILON; + e->origin[1] += DIST_EPSILON; + e->origin[2] += DIST_EPSILON; + } + e->angles[0] = -e->angles[0]; // stupid quake bug + + R_ClearTextureChains (clmodel, chain_model); + for (i=0 ; inummodelsurfaces ; i++, psurf++) + { + pplane = psurf->plane; + dot = DotProduct (modelorg, pplane->normal) - pplane->dist; + if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + R_ChainSurface (psurf, chain_model); + rs_brushpolys++; + } + } + + R_DrawTextureChains (clmodel, e, chain_model); + R_DrawTextureChains_Water (clmodel, e, chain_model); + + glPopMatrix (); +} + +/* +================= +R_DrawBrushModel_ShowTris -- johnfitz +================= +*/ +void R_DrawBrushModel_ShowTris (entity_t *e) +{ + int i; + msurface_t *psurf; + float dot; + mplane_t *pplane; + qmodel_t *clmodel; + glpoly_t *p; + + if (R_CullModelForEntity(e)) + return; + + currententity = e; + clmodel = e->model; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + vec3_t temp; + vec3_t forward, right, up; + + VectorCopy (modelorg, temp); + AngleVectors (e->angles, forward, right, up); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + + glPushMatrix (); + e->angles[0] = -e->angles[0]; // stupid quake bug + R_RotateForEntity (e->origin, e->angles); + e->angles[0] = -e->angles[0]; // stupid quake bug + + // + // draw it + // + for (i=0 ; inummodelsurfaces ; i++, psurf++) + { + pplane = psurf->plane; + dot = DotProduct (modelorg, pplane->normal) - pplane->dist; + if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + if ((psurf->flags & SURF_DRAWTURB) && r_oldwater.value) + for (p = psurf->polys->next; p; p = p->next) + DrawGLTriangleFan (p); + else + DrawGLTriangleFan (psurf->polys); + } + } + + glPopMatrix (); +} + +/* +============================================================= + + LIGHTMAPS + +============================================================= +*/ + +/* +================ +R_RenderDynamicLightmaps +called during rendering +================ +*/ +void R_RenderDynamicLightmaps (msurface_t *fa) +{ + byte *base; + int maps; + glRect_t *theRect; + int smax, tmax; + + if (fa->flags & SURF_DRAWTILED) //johnfitz -- not a lightmapped surface + return; + + // add to lightmap chain + fa->polys->chain = lightmap_polys[fa->lightmaptexturenum]; + lightmap_polys[fa->lightmaptexturenum] = fa->polys; + + // check for lightmap modification + for (maps=0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++) + if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps]) + goto dynamic; + + if (fa->dlightframe == r_framecount // dynamic this frame + || fa->cached_dlight) // dynamic previously + { +dynamic: + if (r_dynamic.value) + { + lightmap_modified[fa->lightmaptexturenum] = true; + theRect = &lightmap_rectchange[fa->lightmaptexturenum]; + if (fa->light_t < theRect->t) { + if (theRect->h) + theRect->h += theRect->t - fa->light_t; + theRect->t = fa->light_t; + } + if (fa->light_s < theRect->l) { + if (theRect->w) + theRect->w += theRect->l - fa->light_s; + theRect->l = fa->light_s; + } + smax = (fa->extents[0]>>4)+1; + tmax = (fa->extents[1]>>4)+1; + if ((theRect->w + theRect->l) < (fa->light_s + smax)) + theRect->w = (fa->light_s-theRect->l)+smax; + if ((theRect->h + theRect->t) < (fa->light_t + tmax)) + theRect->h = (fa->light_t-theRect->t)+tmax; + base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT; + base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; + R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes); + } + } +} + +/* +======================== +AllocBlock -- returns a texture number and the position inside it +======================== +*/ +int AllocBlock (int w, int h, int *x, int *y) +{ + int i, j; + int best, best2; + int texnum; + + // ericw -- rather than searching starting at lightmap 0 every time, + // start at the last lightmap we allocated a surface in. + // This makes AllocBlock much faster on large levels (can shave off 3+ seconds + // of load time on a level with 180 lightmaps), at a cost of not quite packing + // lightmaps as tightly vs. not doing this (uses ~5% more lightmaps) + for (texnum=last_lightmap_allocated ; texnum= best) + break; + if (allocated[texnum][i+j] > best2) + best2 = allocated[texnum][i+j]; + } + if (j == w) + { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if (best + h > BLOCK_HEIGHT) + continue; + + for (i=0 ; iextents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + + surf->lightmaptexturenum = AllocBlock (smax, tmax, &surf->light_s, &surf->light_t); + base = lightmaps + surf->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT; + base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * lightmap_bytes; + R_BuildLightMap (surf, base, BLOCK_WIDTH*lightmap_bytes); +} + +/* +================ +BuildSurfaceDisplayList -- called at level load time +================ +*/ +void BuildSurfaceDisplayList (msurface_t *fa) +{ + int i, lindex, lnumverts; + medge_t *pedges, *r_pedge; + float *vec; + float s, t; + glpoly_t *poly; + +// reconstruct the polygon + pedges = currentmodel->edges; + lnumverts = fa->numedges; + + // + // draw texture + // + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = fa->polys; + fa->polys = poly; + poly->numverts = lnumverts; + + for (i=0 ; isurfedges[fa->firstedge + i]; + + if (lindex > 0) + { + r_pedge = &pedges[lindex]; + vec = r_pcurrentvertbase[r_pedge->v[0]].position; + } + else + { + r_pedge = &pedges[-lindex]; + vec = r_pcurrentvertbase[r_pedge->v[1]].position; + } + s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]; + s /= fa->texinfo->texture->width; + + t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]; + t /= fa->texinfo->texture->height; + + VectorCopy (vec, poly->verts[i]); + poly->verts[i][3] = s; + poly->verts[i][4] = t; + + // + // lightmap texture coordinates + // + s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]; + s -= fa->texturemins[0]; + s += fa->light_s*16; + s += 8; + s /= BLOCK_WIDTH*16; //fa->texinfo->texture->width; + + t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]; + t -= fa->texturemins[1]; + t += fa->light_t*16; + t += 8; + t /= BLOCK_HEIGHT*16; //fa->texinfo->texture->height; + + poly->verts[i][5] = s; + poly->verts[i][6] = t; + } + + //johnfitz -- removed gl_keeptjunctions code + + poly->numverts = lnumverts; +} + +/* +================== +GL_BuildLightmaps -- called at level load time + +Builds the lightmap texture +with all the surfaces from all brush models +================== +*/ +void GL_BuildLightmaps (void) +{ + char name[16]; + byte *data; + int i, j; + qmodel_t *m; + + memset (allocated, 0, sizeof(allocated)); + last_lightmap_allocated = 0; + + r_framecount = 1; // no dlightcache + + //johnfitz -- null out array (the gltexture objects themselves were already freed by Mod_ClearAll) + for (i=0; i < MAX_LIGHTMAPS; i++) + lightmap_textures[i] = NULL; + //johnfitz + + gl_lightmap_format = GL_RGBA;//FIXME: hardcoded for now! + + switch (gl_lightmap_format) + { + case GL_RGBA: + lightmap_bytes = 4; + break; + case GL_BGRA: + lightmap_bytes = 4; + break; + default: + Sys_Error ("GL_BuildLightmaps: bad lightmap format"); + } + + for (j=1 ; jname[0] == '*') + continue; + r_pcurrentvertbase = m->vertexes; + currentmodel = m; + for (i=0 ; inumsurfaces ; i++) + { + //johnfitz -- rewritten to use SURF_DRAWTILED instead of the sky/water flags + if (m->surfaces[i].flags & SURF_DRAWTILED) + continue; + GL_CreateSurfaceLightmap (m->surfaces + i); + BuildSurfaceDisplayList (m->surfaces + i); + //johnfitz + } + } + + // + // upload all lightmaps that were filled + // + for (i=0; i= 64) + Con_DWarning ("%i lightmaps exceeds standard limit of 64 (max = %d).\n", i, MAX_LIGHTMAPS); + //johnfitz +} + +/* +============================================================= + + VBO support + +============================================================= +*/ + +GLuint gl_bmodel_vbo = 0; + +void GL_DeleteBModelVertexBuffer (void) +{ + if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3)) + return; + + GL_DeleteBuffersFunc (1, &gl_bmodel_vbo); + gl_bmodel_vbo = 0; + + GL_ClearBufferBindings (); +} + +/* +================== +GL_BuildBModelVertexBuffer + +Deletes gl_bmodel_vbo if it already exists, then rebuilds it with all +surfaces from world + all brush models +================== +*/ +void GL_BuildBModelVertexBuffer (void) +{ + unsigned int numverts, varray_bytes, varray_index; + int i, j; + qmodel_t *m; + float *varray; + + if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3)) + return; + +// ask GL for a name for our VBO + GL_DeleteBuffersFunc (1, &gl_bmodel_vbo); + GL_GenBuffersFunc (1, &gl_bmodel_vbo); + +// count all verts in all models + numverts = 0; + for (j=1 ; jname[0] == '*' || m->type != mod_brush) + continue; + + for (i=0 ; inumsurfaces ; i++) + { + numverts += m->surfaces[i].numedges; + } + } + +// build vertex array + varray_bytes = VERTEXSIZE * sizeof(float) * numverts; + varray = (float *) malloc (varray_bytes); + varray_index = 0; + + for (j=1 ; jname[0] == '*' || m->type != mod_brush) + continue; + + for (i=0 ; inumsurfaces ; i++) + { + msurface_t *s = &m->surfaces[i]; + s->vbo_firstvert = varray_index; + memcpy (&varray[VERTEXSIZE * varray_index], s->polys->verts, VERTEXSIZE * sizeof(float) * s->numedges); + varray_index += s->numedges; + } + } + +// upload to GPU + GL_BindBufferFunc (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BufferDataFunc (GL_ARRAY_BUFFER, varray_bytes, varray, GL_STATIC_DRAW); + free (varray); + +// invalidate the cached bindings + GL_ClearBufferBindings (); +} + +/* +=============== +R_AddDynamicLights +=============== +*/ +void R_AddDynamicLights (msurface_t *surf) +{ + int lnum; + int sd, td; + float dist, rad, minlight; + vec3_t impact, local; + int s, t; + int i; + int smax, tmax; + mtexinfo_t *tex; + //johnfitz -- lit support via lordhavoc + float cred, cgreen, cblue, brightness; + unsigned *bl; + //johnfitz + + smax = (surf->extents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + tex = surf->texinfo; + + for (lnum=0 ; lnumdlightbits[lnum >> 5] & (1U << (lnum & 31)))) + continue; // not lit by this light + + rad = cl_dlights[lnum].radius; + dist = DotProduct (cl_dlights[lnum].origin, surf->plane->normal) - + surf->plane->dist; + rad -= fabs(dist); + minlight = cl_dlights[lnum].minlight; + if (rad < minlight) + continue; + minlight = rad - minlight; + + for (i=0 ; i<3 ; i++) + { + impact[i] = cl_dlights[lnum].origin[i] - + surf->plane->normal[i]*dist; + } + + local[0] = DotProduct (impact, tex->vecs[0]) + tex->vecs[0][3]; + local[1] = DotProduct (impact, tex->vecs[1]) + tex->vecs[1][3]; + + local[0] -= surf->texturemins[0]; + local[1] -= surf->texturemins[1]; + + //johnfitz -- lit support via lordhavoc + bl = blocklights; + cred = cl_dlights[lnum].color[0] * 256.0f; + cgreen = cl_dlights[lnum].color[1] * 256.0f; + cblue = cl_dlights[lnum].color[2] * 256.0f; + //johnfitz + for (t = 0 ; t td) + dist = sd + (td>>1); + else + dist = td + (sd>>1); + if (dist < minlight) + //johnfitz -- lit support via lordhavoc + { + brightness = rad - dist; + bl[0] += (int) (brightness * cred); + bl[1] += (int) (brightness * cgreen); + bl[2] += (int) (brightness * cblue); + } + bl += 3; + //johnfitz + } + } + } +} + + +/* +=============== +R_BuildLightMap -- johnfitz -- revised for lit support via lordhavoc + +Combine and scale multiple lightmaps into the 8.8 format in blocklights +=============== +*/ +void R_BuildLightMap (msurface_t *surf, byte *dest, int stride) +{ + int smax, tmax; + int r,g,b; + int i, j, size; + byte *lightmap; + unsigned scale; + int maps; + unsigned *bl; + + surf->cached_dlight = (surf->dlightframe == r_framecount); + + smax = (surf->extents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + size = smax*tmax; + lightmap = surf->samples; + + if (cl.worldmodel->lightdata) + { + // clear to no light + memset (&blocklights[0], 0, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc + + // add all the lightmaps + if (lightmap) + { + for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ; + maps++) + { + scale = d_lightstylevalue[surf->styles[maps]]; + surf->cached_light[maps] = scale; // 8.8 fraction + //johnfitz -- lit support via lordhavoc + bl = blocklights; + for (i=0 ; idlightframe == r_framecount) + R_AddDynamicLights (surf); + } + else + { + // set to full bright if no light data + memset (&blocklights[0], 255, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc + } + +// bound, invert, and shift +// store: + switch (gl_lightmap_format) + { + case GL_RGBA: + stride -= smax * 4; + bl = blocklights; + for (i=0 ; i> 8; + g = *bl++ >> 8; + b = *bl++ >> 8; + } + else + { + r = *bl++ >> 7; + g = *bl++ >> 7; + b = *bl++ >> 7; + } + *dest++ = (r > 255)? 255 : r; + *dest++ = (g > 255)? 255 : g; + *dest++ = (b > 255)? 255 : b; + *dest++ = 255; + } + } + break; + case GL_BGRA: + stride -= smax * 4; + bl = blocklights; + for (i=0 ; i> 8; + g = *bl++ >> 8; + b = *bl++ >> 8; + } + else + { + r = *bl++ >> 7; + g = *bl++ >> 7; + b = *bl++ >> 7; + } + *dest++ = (b > 255)? 255 : b; + *dest++ = (g > 255)? 255 : g; + *dest++ = (r > 255)? 255 : r; + *dest++ = 255; + } + } + break; + default: + Sys_Error ("R_BuildLightMap: bad lightmap format"); + } +} + +/* +=============== +R_UploadLightmap -- johnfitz -- uploads the modified lightmap to opengl if necessary + +assumes lightmap texture is already bound +=============== +*/ +static void R_UploadLightmap(int lmap) +{ + glRect_t *theRect; + + if (!lightmap_modified[lmap]) + return; + + lightmap_modified[lmap] = false; + + theRect = &lightmap_rectchange[lmap]; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, theRect->t, BLOCK_WIDTH, theRect->h, gl_lightmap_format, + GL_UNSIGNED_BYTE, lightmaps+(lmap* BLOCK_HEIGHT + theRect->t) *BLOCK_WIDTH*lightmap_bytes); + theRect->l = BLOCK_WIDTH; + theRect->t = BLOCK_HEIGHT; + theRect->h = 0; + theRect->w = 0; + + rs_dynamiclightmaps++; +} + +void R_UploadLightmaps (void) +{ + int lmap; + + for (lmap = 0; lmap < MAX_LIGHTMAPS; lmap++) + { + if (!lightmap_modified[lmap]) + continue; + + GL_Bind (lightmap_textures[lmap]); + R_UploadLightmap(lmap); + } +} + +/* +================ +R_RebuildAllLightmaps -- johnfitz -- called when gl_overbright gets toggled +================ +*/ +void R_RebuildAllLightmaps (void) +{ + int i, j; + qmodel_t *mod; + msurface_t *fa; + byte *base; + + if (!cl.worldmodel) // is this the correct test? + return; + + //for each surface in each model, rebuild lightmap with new scale + for (i=1; isurfaces[mod->firstmodelsurface]; + for (j=0; jnummodelsurfaces; j++, fa++) + { + if (fa->flags & SURF_DRAWTILED) + continue; + base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT; + base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; + R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes); + } + } + + //for each lightmap, upload it + for (i=0; i 255 ? 255 : r; + a = sharpness * (255 - r); + a = q_min(a,255); + return a; +} + +/* +=============== +R_InitParticleTextures -- johnfitz -- rewritten +=============== +*/ +void R_InitParticleTextures (void) +{ + int x,y; + static byte particle1_data[64*64*4]; + static byte particle2_data[2*2*4]; + static byte particle3_data[64*64*4]; + byte *dst; + + // particle texture 1 -- circle + dst = particle1_data; + for (x=0 ; x<64 ; x++) + for (y=0 ; y<64 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = R_ParticleTextureLookup(x, y, 8); + } + particletexture1 = TexMgr_LoadImage (NULL, "particle1", 64, 64, SRC_RGBA, particle1_data, "", (src_offset_t)particle1_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_LINEAR); + + // particle texture 2 -- square + dst = particle2_data; + for (x=0 ; x<2 ; x++) + for (y=0 ; y<2 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = x || y ? 0 : 255; + } + particletexture2 = TexMgr_LoadImage (NULL, "particle2", 2, 2, SRC_RGBA, particle2_data, "", (src_offset_t)particle2_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_NEAREST); + + // particle texture 3 -- blob + dst = particle3_data; + for (x=0 ; x<64 ; x++) + for (y=0 ; y<64 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = R_ParticleTextureLookup(x, y, 2); + } + particletexture3 = TexMgr_LoadImage (NULL, "particle3", 64, 64, SRC_RGBA, particle3_data, "", (src_offset_t)particle3_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_LINEAR); + + //set default + particletexture = particletexture1; + texturescalefactor = 1.27; +} + +/* +=============== +R_SetParticleTexture_f -- johnfitz +=============== +*/ +static void R_SetParticleTexture_f (cvar_t *var) +{ + switch ((int)(r_particles.value)) + { + case 1: + particletexture = particletexture1; + texturescalefactor = 1.27; + break; + case 2: + particletexture = particletexture2; + texturescalefactor = 1.0; + break; +// case 3: +// particletexture = particletexture3; +// texturescalefactor = 1.5; +// break; + } +} + +/* +=============== +R_InitParticles +=============== +*/ +void R_InitParticles (void) +{ + int i; + + i = COM_CheckParm ("-particles"); + + if (i) + { + r_numparticles = (int)(Q_atoi(com_argv[i+1])); + if (r_numparticles < ABSOLUTE_MIN_PARTICLES) + r_numparticles = ABSOLUTE_MIN_PARTICLES; + } + else + { + r_numparticles = MAX_PARTICLES; + } + + particles = (particle_t *) + Hunk_AllocName (r_numparticles * sizeof(particle_t), "particles"); + + Cvar_RegisterVariable (&r_particles); //johnfitz + Cvar_SetCallback (&r_particles, R_SetParticleTexture_f); + Cvar_RegisterVariable (&r_quadparticles); //johnfitz + + R_InitParticleTextures (); //johnfitz +} + +/* +=============== +R_EntityParticles +=============== +*/ +#define NUMVERTEXNORMALS 162 +extern float r_avertexnormals[NUMVERTEXNORMALS][3]; +vec3_t avelocities[NUMVERTEXNORMALS]; +float beamlength = 16; +vec3_t avelocity = {23, 7, 3}; +float partstep = 0.01; +float timescale = 0.01; + +void R_EntityParticles (entity_t *ent) +{ + int i; + particle_t *p; + float angle; + float sp, sy, cp, cy; +// float sr, cr; +// int count; + vec3_t forward; + float dist; + + dist = 64; +// count = 50; + + if (!avelocities[0][0]) + { + for (i = 0; i < NUMVERTEXNORMALS; i++) + { + avelocities[i][0] = (rand() & 255) * 0.01; + avelocities[i][1] = (rand() & 255) * 0.01; + avelocities[i][2] = (rand() & 255) * 0.01; + } + } + + for (i = 0; i < NUMVERTEXNORMALS; i++) + { + angle = cl.time * avelocities[i][0]; + sy = sin(angle); + cy = cos(angle); + angle = cl.time * avelocities[i][1]; + sp = sin(angle); + cp = cos(angle); + angle = cl.time * avelocities[i][2]; + // sr = sin(angle); + // cr = cos(angle); + + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.01; + p->color = 0x6f; + p->type = pt_explode; + + p->org[0] = ent->origin[0] + r_avertexnormals[i][0]*dist + forward[0]*beamlength; + p->org[1] = ent->origin[1] + r_avertexnormals[i][1]*dist + forward[1]*beamlength; + p->org[2] = ent->origin[2] + r_avertexnormals[i][2]*dist + forward[2]*beamlength; + } +} + +/* +=============== +R_ClearParticles +=============== +*/ +void R_ClearParticles (void) +{ + int i; + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;inext; + p->next = active_particles; + active_particles = p; + + p->die = 99999; + p->color = (-c)&15; + p->type = pt_static; + VectorCopy (vec3_origin, p->vel); + VectorCopy (org, p->org); + } + + fclose (f); + Con_Printf ("%i points read\n", c); +} + +/* +=============== +R_ParseParticleEffect + +Parse an effect out of the server message +=============== +*/ +void R_ParseParticleEffect (void) +{ + vec3_t org, dir; + int i, count, msgcount, color; + + for (i=0 ; i<3 ; i++) + org[i] = MSG_ReadCoord (cl.protocolflags); + for (i=0 ; i<3 ; i++) + dir[i] = MSG_ReadChar () * (1.0/16); + msgcount = MSG_ReadByte (); + color = MSG_ReadByte (); + + if (msgcount == 255) + count = 1024; + else + count = msgcount; + + R_RunParticleEffect (org, dir, color, count); +} + +/* +=============== +R_ParticleExplosion +=============== +*/ +void R_ParticleExplosion (vec3_t org) +{ + int i, j; + particle_t *p; + + for (i=0 ; i<1024 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 5; + p->color = ramp1[0]; + p->ramp = rand()&3; + if (i & 1) + { + p->type = pt_explode; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_explode2; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } +} + +/* +=============== +R_ParticleExplosion2 +=============== +*/ +void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength) +{ + int i, j; + particle_t *p; + int colorMod = 0; + + for (i=0; i<512; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.3; + p->color = colorStart + (colorMod % colorLength); + colorMod++; + + p->type = pt_blob; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } +} + +/* +=============== +R_BlobExplosion +=============== +*/ +void R_BlobExplosion (vec3_t org) +{ + int i, j; + particle_t *p; + + for (i=0 ; i<1024 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 1 + (rand()&8)*0.05; + + if (i & 1) + { + p->type = pt_blob; + p->color = 66 + rand()%6; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_blob2; + p->color = 150 + rand()%6; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } +} + +/* +=============== +R_RunParticleEffect +=============== +*/ +void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) +{ + int i, j; + particle_t *p; + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + if (count == 1024) + { // rocket explosion + p->die = cl.time + 5; + p->color = ramp1[0]; + p->ramp = rand()&3; + if (i & 1) + { + p->type = pt_explode; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_explode2; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } + else + { + p->die = cl.time + 0.1*(rand()%5); + p->color = (color&~7) + (rand()&7); + p->type = pt_slowgrav; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&15)-8); + p->vel[j] = dir[j]*15;// + (rand()%300)-150; + } + } + } +} + +/* +=============== +R_LavaSplash +=============== +*/ +void R_LavaSplash (vec3_t org) +{ + int i, j, k; + particle_t *p; + float vel; + vec3_t dir; + + for (i=-16 ; i<16 ; i++) + for (j=-16 ; j<16 ; j++) + for (k=0 ; k<1 ; k++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 2 + (rand()&31) * 0.02; + p->color = 224 + (rand()&7); + p->type = pt_slowgrav; + + dir[0] = j*8 + (rand()&7); + dir[1] = i*8 + (rand()&7); + dir[2] = 256; + + p->org[0] = org[0] + dir[0]; + p->org[1] = org[1] + dir[1]; + p->org[2] = org[2] + (rand()&63); + + VectorNormalize (dir); + vel = 50 + (rand()&63); + VectorScale (dir, vel, p->vel); + } +} + +/* +=============== +R_TeleportSplash +=============== +*/ +void R_TeleportSplash (vec3_t org) +{ + int i, j, k; + particle_t *p; + float vel; + vec3_t dir; + + for (i=-16 ; i<16 ; i+=4) + for (j=-16 ; j<16 ; j+=4) + for (k=-24 ; k<32 ; k+=4) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.2 + (rand()&7) * 0.02; + p->color = 7 + (rand()&7); + p->type = pt_slowgrav; + + dir[0] = j*8; + dir[1] = i*8; + dir[2] = k*8; + + p->org[0] = org[0] + i + (rand()&3); + p->org[1] = org[1] + j + (rand()&3); + p->org[2] = org[2] + k + (rand()&3); + + VectorNormalize (dir); + vel = 50 + (rand()&63); + VectorScale (dir, vel, p->vel); + } +} + +/* +=============== +R_RocketTrail + +FIXME -- rename function and use #defined types instead of numbers +=============== +*/ +void R_RocketTrail (vec3_t start, vec3_t end, int type) +{ + vec3_t vec; + float len; + int j; + particle_t *p; + int dec; + static int tracercount; + + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + if (type < 128) + dec = 3; + else + { + dec = 1; + type -= 128; + } + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + VectorCopy (vec3_origin, p->vel); + p->die = cl.time + 2; + + switch (type) + { + case 0: // rocket trail + p->ramp = (rand()&3); + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 1: // smoke smoke + p->ramp = (rand()&3) + 2; + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 2: // blood + p->type = pt_grav; + p->color = 67 + (rand()&3); + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 3: + case 5: // tracer + p->die = cl.time + 0.5; + p->type = pt_static; + if (type == 3) + p->color = 52 + ((tracercount&4)<<1); + else + p->color = 230 + ((tracercount&4)<<1); + + tracercount++; + + VectorCopy (start, p->org); + if (tracercount & 1) + { + p->vel[0] = 30*vec[1]; + p->vel[1] = 30*-vec[0]; + } + else + { + p->vel[0] = 30*-vec[1]; + p->vel[1] = 30*vec[0]; + } + break; + + case 4: // slight blood + p->type = pt_grav; + p->color = 67 + (rand()&3); + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + len -= 3; + break; + + case 6: // voor trail + p->color = 9*16 + 8 + (rand()&3); + p->type = pt_static; + p->die = cl.time + 0.3; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()&15)-8); + break; + } + + VectorAdd (start, vec, start); + } +} + +/* +=============== +CL_RunParticles -- johnfitz -- all the particle behavior, separated from R_DrawParticles +=============== +*/ +void CL_RunParticles (void) +{ + particle_t *p, *kill; + int i; + float time1, time2, time3, dvel, frametime, grav; + extern cvar_t sv_gravity; + + frametime = cl.time - cl.oldtime; + time3 = frametime * 15; + time2 = frametime * 10; + time1 = frametime * 5; + grav = frametime * sv_gravity.value * 0.05; + dvel = 4*frametime; + + for ( ;; ) + { + kill = active_particles; + if (kill && kill->die < cl.time) + { + active_particles = kill->next; + kill->next = free_particles; + free_particles = kill; + continue; + } + break; + } + + for (p=active_particles ; p ; p=p->next) + { + for ( ;; ) + { + kill = p->next; + if (kill && kill->die < cl.time) + { + p->next = kill->next; + kill->next = free_particles; + free_particles = kill; + continue; + } + break; + } + + p->org[0] += p->vel[0]*frametime; + p->org[1] += p->vel[1]*frametime; + p->org[2] += p->vel[2]*frametime; + + switch (p->type) + { + case pt_static: + break; + case pt_fire: + p->ramp += time1; + if (p->ramp >= 6) + p->die = -1; + else + p->color = ramp3[(int)p->ramp]; + p->vel[2] += grav; + break; + + case pt_explode: + p->ramp += time2; + if (p->ramp >=8) + p->die = -1; + else + p->color = ramp1[(int)p->ramp]; + for (i=0 ; i<3 ; i++) + p->vel[i] += p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_explode2: + p->ramp += time3; + if (p->ramp >=8) + p->die = -1; + else + p->color = ramp2[(int)p->ramp]; + for (i=0 ; i<3 ; i++) + p->vel[i] -= p->vel[i]*frametime; + p->vel[2] -= grav; + break; + + case pt_blob: + for (i=0 ; i<3 ; i++) + p->vel[i] += p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_blob2: + for (i=0 ; i<2 ; i++) + p->vel[i] -= p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_grav: + case pt_slowgrav: + p->vel[2] -= grav; + break; + } + } +} + +/* +=============== +R_DrawParticles -- johnfitz -- moved all non-drawing code to CL_RunParticles +=============== +*/ +void R_DrawParticles (void) +{ + particle_t *p; + float scale; + vec3_t up, right, p_up, p_right, p_upright; //johnfitz -- p_ vectors + GLubyte color[4], *c; //johnfitz -- particle transparency + extern cvar_t r_particles; //johnfitz + //float alpha; //johnfitz -- particle transparency + + if (!r_particles.value) + return; + + //ericw -- avoid empty glBegin(),glEnd() pair below; causes issues on AMD + if (!active_particles) + return; + + VectorScale (vup, 1.5, up); + VectorScale (vright, 1.5, right); + + GL_Bind(particletexture); + glEnable (GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDepthMask (GL_FALSE); //johnfitz -- fix for particle z-buffer bug + + if (r_quadparticles.value) //johnitz -- quads save fillrate + { + glBegin (GL_QUADS); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale /= 2.0; //quad is half the size of triangle + + scale *= texturescalefactor; //johnfitz -- compensate for apparent size of different particle textures + + //johnfitz -- particle transparency and fade out + c = (GLubyte *) &d_8to24table[(int)p->color]; + color[0] = c[0]; + color[1] = c[1]; + color[2] = c[2]; + //alpha = CLAMP(0, p->die + 0.5 - cl.time, 1); + color[3] = 255; //(int)(alpha * 255); + glColor4ubv(color); + //johnfitz + + glTexCoord2f (0,0); + glVertex3fv (p->org); + + glTexCoord2f (0.5,0); + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + glTexCoord2f (0.5,0.5); + VectorMA (p_up, scale, right, p_upright); + glVertex3fv (p_upright); + + glTexCoord2f (0,0.5); + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + + rs_particles++; //johnfitz //FIXME: just use r_numparticles + } + glEnd (); + } + else //johnitz -- triangles save verts + { + glBegin (GL_TRIANGLES); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale *= texturescalefactor; //johnfitz -- compensate for apparent size of different particle textures + + //johnfitz -- particle transparency and fade out + c = (GLubyte *) &d_8to24table[(int)p->color]; + color[0] = c[0]; + color[1] = c[1]; + color[2] = c[2]; + //alpha = CLAMP(0, p->die + 0.5 - cl.time, 1); + color[3] = 255; //(int)(alpha * 255); + glColor4ubv(color); + //johnfitz + + glTexCoord2f (0,0); + glVertex3fv (p->org); + + glTexCoord2f (1,0); + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + glTexCoord2f (0,1); + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + + rs_particles++; //johnfitz //FIXME: just use r_numparticles + } + glEnd (); + } + + glDepthMask (GL_TRUE); //johnfitz -- fix for particle z-buffer bug + glDisable (GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1,1,1); +} + + +/* +=============== +R_DrawParticles_ShowTris -- johnfitz +=============== +*/ +void R_DrawParticles_ShowTris (void) +{ + particle_t *p; + float scale; + vec3_t up, right, p_up, p_right, p_upright; + extern cvar_t r_particles; + + if (!r_particles.value) + return; + + VectorScale (vup, 1.5, up); + VectorScale (vright, 1.5, right); + + if (r_quadparticles.value) + { + for (p=active_particles ; p ; p=p->next) + { + glBegin (GL_TRIANGLE_FAN); + + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale /= 2.0; //quad is half the size of triangle + + scale *= texturescalefactor; //compensate for apparent size of different particle textures + + glVertex3fv (p->org); + + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + VectorMA (p_up, scale, right, p_upright); + glVertex3fv (p_upright); + + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + + glEnd (); + } + } + else + { + glBegin (GL_TRIANGLES); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale *= texturescalefactor; //compensate for apparent size of different particle textures + + glVertex3fv (p->org); + + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + } + glEnd (); + } +} + diff --git a/source/r_sprite.c b/source/r_sprite.c new file mode 100644 index 0000000..6765e97 --- /dev/null +++ b/source/r_sprite.c @@ -0,0 +1,182 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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_sprite.c -- sprite model rendering + +#include "quakedef.h" + +/* +================ +R_GetSpriteFrame +================ +*/ +mspriteframe_t *R_GetSpriteFrame (entity_t *currentent) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe; + int i, numframes, frame; + float *pintervals, fullinterval, targettime, time; + + psprite = (msprite_t *) currentent->model->cache.data; + frame = currentent->frame; + + if ((frame >= psprite->numframes) || (frame < 0)) + { + Con_DPrintf ("R_DrawSprite: no such frame %d for '%s'\n", frame, currentent->model->name); + frame = 0; + } + + if (psprite->frames[frame].type == SPR_SINGLE) + { + pspriteframe = psprite->frames[frame].frameptr; + } + else + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + + time = cl.time + currentent->syncbase; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by 0 + targettime = time - ((int)(time / fullinterval)) * fullinterval; + + for (i=0 ; i<(numframes-1) ; i++) + { + if (pintervals[i] > targettime) + break; + } + + pspriteframe = pspritegroup->frames[i]; + } + + return pspriteframe; +} + +/* +================= +R_DrawSpriteModel -- johnfitz -- rewritten: now supports all orientations +================= +*/ +void R_DrawSpriteModel (entity_t *e) +{ + vec3_t point, v_forward, v_right, v_up; + msprite_t *psprite; + mspriteframe_t *frame; + float *s_up, *s_right; + float angle, sr, cr; + + //TODO: frustum cull it? + + frame = R_GetSpriteFrame (e); + psprite = (msprite_t *) currententity->model->cache.data; + + switch(psprite->type) + { + case SPR_VP_PARALLEL_UPRIGHT: //faces view plane, up is towards the heavens + v_up[0] = 0; + v_up[1] = 0; + v_up[2] = 1; + s_up = v_up; + s_right = vright; + break; + case SPR_FACING_UPRIGHT: //faces camera origin, up is towards the heavens + VectorSubtract(currententity->origin, r_origin, v_forward); + v_forward[2] = 0; + VectorNormalizeFast(v_forward); + v_right[0] = v_forward[1]; + v_right[1] = -v_forward[0]; + v_right[2] = 0; + v_up[0] = 0; + v_up[1] = 0; + v_up[2] = 1; + s_up = v_up; + s_right = v_right; + break; + case SPR_VP_PARALLEL: //faces view plane, up is towards the top of the screen + s_up = vup; + s_right = vright; + break; + case SPR_ORIENTED: //pitch yaw roll are independent of camera + AngleVectors (currententity->angles, v_forward, v_right, v_up); + s_up = v_up; + s_right = v_right; + break; + case SPR_VP_PARALLEL_ORIENTED: //faces view plane, but obeys roll value + angle = currententity->angles[ROLL] * M_PI_DIV_180; + sr = sin(angle); + cr = cos(angle); + v_right[0] = vright[0] * cr + vup[0] * sr; + v_right[1] = vright[1] * cr + vup[1] * sr; + v_right[2] = vright[2] * cr + vup[2] * sr; + v_up[0] = vright[0] * -sr + vup[0] * cr; + v_up[1] = vright[1] * -sr + vup[1] * cr; + v_up[2] = vright[2] * -sr + vup[2] * cr; + s_up = v_up; + s_right = v_right; + break; + default: + return; + } + + //johnfitz: offset decals + if (psprite->type == SPR_ORIENTED) + GL_PolygonOffset (OFFSET_DECAL); + + glColor3f (1,1,1); + + GL_DisableMultitexture(); + + GL_Bind(frame->gltexture); + + glEnable (GL_ALPHA_TEST); + glBegin (GL_TRIANGLE_FAN); //was GL_QUADS, but changed to support r_showtris + + glTexCoord2f (0, frame->tmax); + VectorMA (e->origin, frame->down, s_up, point); + VectorMA (point, frame->left, s_right, point); + glVertex3fv (point); + + glTexCoord2f (0, 0); + VectorMA (e->origin, frame->up, s_up, point); + VectorMA (point, frame->left, s_right, point); + glVertex3fv (point); + + glTexCoord2f (frame->smax, 0); + VectorMA (e->origin, frame->up, s_up, point); + VectorMA (point, frame->right, s_right, point); + glVertex3fv (point); + + glTexCoord2f (frame->smax, frame->tmax); + VectorMA (e->origin, frame->down, s_up, point); + VectorMA (point, frame->right, s_right, point); + glVertex3fv (point); + + glEnd (); + glDisable (GL_ALPHA_TEST); + + //johnfitz: offset decals + if (psprite->type == SPR_ORIENTED) + GL_PolygonOffset (OFFSET_NONE); +} diff --git a/source/r_world.c b/source/r_world.c new file mode 100644 index 0000000..d9e4038 --- /dev/null +++ b/source/r_world.c @@ -0,0 +1,1234 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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_world.c: world model rendering + +#include "quakedef.h" + +extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater, r_oldskyleaf, r_showtris; //johnfitz + +extern glpoly_t *lightmap_polys[MAX_LIGHTMAPS]; + +byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel); + +int vis_changed; //if true, force pvs to be refreshed + +//============================================================================== +// +// SETUP CHAINS +// +//============================================================================== + +/* +================ +R_ClearTextureChains -- ericw + +clears texture chains for all textures used by the given model, and also +clears the lightmap chains +================ +*/ +void R_ClearTextureChains (qmodel_t *mod, texchain_t chain) +{ + int i; + + // set all chains to null + for (i=0 ; inumtextures ; i++) + if (mod->textures[i]) + mod->textures[i]->texturechains[chain] = NULL; + + // clear lightmap chains + memset (lightmap_polys, 0, sizeof(lightmap_polys)); +} + +/* +================ +R_ChainSurface -- ericw -- adds the given surface to its texture chain +================ +*/ +void R_ChainSurface (msurface_t *surf, texchain_t chain) +{ + surf->texturechain = surf->texinfo->texture->texturechains[chain]; + surf->texinfo->texture->texturechains[chain] = surf; +} + +/* +=============== +R_MarkSurfaces -- johnfitz -- mark surfaces based on PVS and rebuild texture chains +=============== +*/ +void R_MarkSurfaces (void) +{ + byte *vis; + mleaf_t *leaf; + mnode_t *node; + msurface_t *surf, **mark; + int i, j; + qboolean nearwaterportal; + + // clear lightmap chains + memset (lightmap_polys, 0, sizeof(lightmap_polys)); + + // check this leaf for water portals + // TODO: loop through all water surfs and use distance to leaf cullbox + nearwaterportal = false; + for (i=0, mark = r_viewleaf->firstmarksurface; i < r_viewleaf->nummarksurfaces; i++, mark++) + if ((*mark)->flags & SURF_DRAWTURB) + nearwaterportal = true; + + // choose vis data + if (r_novis.value || r_viewleaf->contents == CONTENTS_SOLID || r_viewleaf->contents == CONTENTS_SKY) + vis = Mod_NoVisPVS (cl.worldmodel); + else if (nearwaterportal) + vis = SV_FatPVS (r_origin, cl.worldmodel); + else + vis = Mod_LeafPVS (r_viewleaf, cl.worldmodel); + + // if surface chains don't need regenerating, just add static entities and return + if (r_oldviewleaf == r_viewleaf && !vis_changed && !nearwaterportal) + { + leaf = &cl.worldmodel->leafs[1]; + for (i=0 ; inumleafs ; i++, leaf++) + if (vis[i>>3] & (1<<(i&7))) + if (leaf->efrags) + R_StoreEfrags (&leaf->efrags); + return; + } + + vis_changed = false; + r_visframecount++; + r_oldviewleaf = r_viewleaf; + + // iterate through leaves, marking surfaces + leaf = &cl.worldmodel->leafs[1]; + for (i=0 ; inumleafs ; i++, leaf++) + { + if (vis[i>>3] & (1<<(i&7))) + { + if (r_oldskyleaf.value || leaf->contents != CONTENTS_SKY) + for (j=0, mark = leaf->firstmarksurface; jnummarksurfaces; j++, mark++) + (*mark)->visframe = r_visframecount; + + // add static models + if (leaf->efrags) + R_StoreEfrags (&leaf->efrags); + } + } + + // set all chains to null + for (i=0 ; inumtextures ; i++) + if (cl.worldmodel->textures[i]) + cl.worldmodel->textures[i]->texturechains[chain_world] = NULL; + + // rebuild chains + +#if 1 + //iterate through surfaces one node at a time to rebuild chains + //need to do it this way if we want to work with tyrann's skip removal tool + //becuase his tool doesn't actually remove the surfaces from the bsp surfaces lump + //nor does it remove references to them in each leaf's marksurfaces list + for (i=0, node = cl.worldmodel->nodes ; inumnodes ; i++, node++) + for (j=0, surf=&cl.worldmodel->surfaces[node->firstsurface] ; jnumsurfaces ; j++, surf++) + if (surf->visframe == r_visframecount) + { + R_ChainSurface(surf, chain_world); + } +#else + //the old way + surf = &cl.worldmodel->surfaces[cl.worldmodel->firstmodelsurface]; + for (i=0 ; inummodelsurfaces ; i++, surf++) + { + if (surf->visframe == r_visframecount) + { + R_ChainSurface(surf, chain_world); + } + } +#endif +} + +/* +================ +R_BackFaceCull -- johnfitz -- returns true if the surface is facing away from vieworg +================ +*/ +qboolean R_BackFaceCull (msurface_t *surf) +{ + double dot; + + switch (surf->plane->type) + { + case PLANE_X: + dot = r_refdef.vieworg[0] - surf->plane->dist; + break; + case PLANE_Y: + dot = r_refdef.vieworg[1] - surf->plane->dist; + break; + case PLANE_Z: + dot = r_refdef.vieworg[2] - surf->plane->dist; + break; + default: + dot = DotProduct (r_refdef.vieworg, surf->plane->normal) - surf->plane->dist; + break; + } + + if ((dot < 0) ^ !!(surf->flags & SURF_PLANEBACK)) + return true; + + return false; +} + +/* +================ +R_CullSurfaces -- johnfitz +================ +*/ +void R_CullSurfaces (void) +{ + msurface_t *s; + int i; + texture_t *t; + + if (!r_drawworld_cheatsafe) + return; + +// ericw -- instead of testing (s->visframe == r_visframecount) on all world +// surfaces, use the chained surfaces, which is exactly the same set of sufaces + for (i=0 ; inumtextures ; i++) + { + t = cl.worldmodel->textures[i]; + + if (!t || !t->texturechains[chain_world]) + continue; + + for (s = t->texturechains[chain_world]; s; s = s->texturechain) + { + if (R_CullBox(s->mins, s->maxs) || R_BackFaceCull (s)) + s->culled = true; + else + { + s->culled = false; + rs_brushpolys++; //count wpolys here + if (s->texinfo->texture->warpimage) + s->texinfo->texture->update_warp = true; + } + } + } +} + +/* +================ +R_BuildLightmapChains -- johnfitz -- used for r_lightmap 1 + +ericw -- now always used at the start of R_DrawTextureChains for the +mh dynamic lighting speedup +================ +*/ +void R_BuildLightmapChains (qmodel_t *model, texchain_t chain) +{ + texture_t *t; + msurface_t *s; + int i; + + // clear lightmap chains (already done in r_marksurfaces, but clearing them here to be safe becuase of r_stereo) + memset (lightmap_polys, 0, sizeof(lightmap_polys)); + + // now rebuild them + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain]) + continue; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + R_RenderDynamicLightmaps (s); + } +} + +//============================================================================== +// +// DRAW CHAINS +// +//============================================================================== + +/* +============= +R_BeginTransparentDrawing -- ericw +============= +*/ +static void R_BeginTransparentDrawing (float entalpha) +{ + if (entalpha < 1.0f) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f (1,1,1,entalpha); + } +} + +/* +============= +R_EndTransparentDrawing -- ericw +============= +*/ +static void R_EndTransparentDrawing (float entalpha) +{ + if (entalpha < 1.0f) + { + glDepthMask (GL_TRUE); + glDisable (GL_BLEND); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f (1, 1, 1); + } +} + +/* +================ +R_DrawTextureChains_ShowTris -- johnfitz +================ +*/ +void R_DrawTextureChains_ShowTris (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t) + continue; + + if (r_oldwater.value && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB)) + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + for (p = s->polys->next; p; p = p->next) + { + DrawGLTriangleFan (p); + } + } + else + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + DrawGLTriangleFan (s->polys); + } + } + } +} + +/* +================ +R_DrawTextureChains_Drawflat -- johnfitz +================ +*/ +void R_DrawTextureChains_Drawflat (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t) + continue; + + if (r_oldwater.value && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB)) + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + for (p = s->polys->next; p; p = p->next) + { + srand((unsigned int) (uintptr_t) p); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (p); + rs_brushpasses++; + } + } + else + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + srand((unsigned int) (uintptr_t) s->polys); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } + } + glColor3f (1,1,1); + srand ((int) (cl.time * 1000)); +} + +/* +================ +R_DrawTextureChains_Glow -- johnfitz +================ +*/ +void R_DrawTextureChains_Glow (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + gltexture_t *glt; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(glt = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind (glt); + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } +} + +//============================================================================== +// +// VBO SUPPORT +// +//============================================================================== + +static unsigned int R_NumTriangleIndicesForSurf (msurface_t *s) +{ + return 3 * (s->numedges - 2); +} + +/* +================ +R_TriangleIndicesForSurf + +Writes out the triangle indices needed to draw s as a triangle list. +The number of indices it will write is given by R_NumTriangleIndicesForSurf. +================ +*/ +static void R_TriangleIndicesForSurf (msurface_t *s, unsigned int *dest) +{ + int i; + for (i=2; inumedges; i++) + { + *dest++ = s->vbo_firstvert; + *dest++ = s->vbo_firstvert + i - 1; + *dest++ = s->vbo_firstvert + i; + } +} + +#define MAX_BATCH_SIZE 4096 + +static unsigned int vbo_indices[MAX_BATCH_SIZE]; +static unsigned int num_vbo_indices; + +/* +================ +R_ClearBatch +================ +*/ +static void R_ClearBatch () +{ + num_vbo_indices = 0; +} + +/* +================ +R_FlushBatch + +Draw the current batch if non-empty and clears it, ready for more R_BatchSurface calls. +================ +*/ +static void R_FlushBatch () +{ + if (num_vbo_indices > 0) + { + glDrawElements (GL_TRIANGLES, num_vbo_indices, GL_UNSIGNED_INT, vbo_indices); + num_vbo_indices = 0; + } +} + +/* +================ +R_BatchSurface + +Add the surface to the current batch, or just draw it immediately if we're not +using VBOs. +================ +*/ +static void R_BatchSurface (msurface_t *s) +{ + int num_surf_indices; + + num_surf_indices = R_NumTriangleIndicesForSurf (s); + + if (num_vbo_indices + num_surf_indices > MAX_BATCH_SIZE) + R_FlushBatch(); + + R_TriangleIndicesForSurf (s, &vbo_indices[num_vbo_indices]); + num_vbo_indices += num_surf_indices; +} + +/* +================ +R_DrawTextureChains_Multitexture -- johnfitz +================ +*/ +void R_DrawTextureChains_Multitexture (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i, j; + msurface_t *s; + texture_t *t; + float *v; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTILED | SURF_NOTEXTURE)) + continue; + + bound = false; + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip alpha test back on + + GL_EnableMultitexture(); // selects TEXTURE1 + bound = true; + } + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + glBegin(GL_POLYGON); + v = s->polys->verts[0]; + for (j=0 ; jpolys->numverts ; j++, v+= VERTEXSIZE) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + rs_brushpasses++; + } + GL_DisableMultitexture(); // selects TEXTURE0 + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + } +} + +/* +================ +R_DrawTextureChains_NoTexture -- johnfitz + +draws surfs whose textures were missing from the BSP +================ +*/ +void R_DrawTextureChains_NoTexture (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_NOTEXTURE)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind (t->gltexture); + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } +} + +/* +================ +R_DrawTextureChains_TextureOnly -- johnfitz +================ +*/ +void R_DrawTextureChains_TextureOnly (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWSKY)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip alpha test back on + + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + } +} + +/* +================ +GL_WaterAlphaForEntitySurface -- ericw + +Returns the water alpha to use for the entity and surface combination. +================ +*/ +float GL_WaterAlphaForEntitySurface (entity_t *ent, msurface_t *s) +{ + float entalpha; + if (ent == NULL || ent->alpha == ENTALPHA_DEFAULT) + entalpha = GL_WaterAlphaForSurface(s); + else + entalpha = ENTALPHA_DECODE(ent->alpha); + return entalpha; +} + +/* +================ +R_DrawTextureChains_Water -- johnfitz +================ +*/ +void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + qboolean bound; + float entalpha; + + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) // ericw -- !r_drawworld_cheatsafe check moved to R_DrawWorld_Water () + return; + + if (r_oldwater.value) + { + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB)) + continue; + bound = false; + entalpha = 1.0f; + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + entalpha = GL_WaterAlphaForEntitySurface (ent, s); + R_BeginTransparentDrawing (entalpha); + GL_Bind (t->gltexture); + bound = true; + } + for (p = s->polys->next; p; p = p->next) + { + DrawWaterPoly (p); + rs_brushpasses++; + } + } + R_EndTransparentDrawing (entalpha); + } + } + else + { + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB)) + continue; + bound = false; + entalpha = 1.0f; + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + entalpha = GL_WaterAlphaForEntitySurface (ent, s); + R_BeginTransparentDrawing (entalpha); + GL_Bind (t->warpimage); + + if (model != cl.worldmodel) + { + // ericw -- this is copied from R_DrawSequentialPoly. + // If the poly is not part of the world we have to + // set this flag + t->update_warp = true; // FIXME: one frame too late! + } + + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + R_EndTransparentDrawing (entalpha); + } + } +} + +/* +================ +R_DrawTextureChains_White -- johnfitz -- draw sky and water as white polys when r_lightmap is 1 +================ +*/ +void R_DrawTextureChains_White (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + + glDisable (GL_TEXTURE_2D); + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTILED)) + continue; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } + glEnable (GL_TEXTURE_2D); +} + +/* +================ +R_DrawLightmapChains -- johnfitz -- R_BlendLightmaps stripped down to almost nothing +================ +*/ +void R_DrawLightmapChains (void) +{ + int i, j; + glpoly_t *p; + float *v; + + for (i=0 ; ichain) + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (j=0 ; jnumverts ; j++, v+= VERTEXSIZE) + { + glTexCoord2f (v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + rs_brushpasses++; + } + } +} + +static GLuint r_world_program; + +// uniforms used in vert shader + +// uniforms used in frag shader +static GLuint texLoc; +static GLuint LMTexLoc; +static GLuint fullbrightTexLoc; +static GLuint useFullbrightTexLoc; +static GLuint useOverbrightLoc; +static GLuint useAlphaTestLoc; +static GLuint alphaLoc; + +#define vertAttrIndex 0 +#define texCoordsAttrIndex 1 +#define LMCoordsAttrIndex 2 + +/* +============= +GLWorld_CreateShaders +============= +*/ +void GLWorld_CreateShaders (void) +{ + const glsl_attrib_binding_t bindings[] = { + { "Vert", vertAttrIndex }, + { "TexCoords", texCoordsAttrIndex }, + { "LMCoords", LMCoordsAttrIndex } + }; + + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "attribute vec3 Vert;\n" + "attribute vec2 TexCoords;\n" + "attribute vec2 LMCoords;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " gl_TexCoord[0] = vec4(TexCoords, 0.0, 0.0);\n" + " gl_TexCoord[1] = vec4(LMCoords, 0.0, 0.0);\n" + " gl_Position = gl_ModelViewProjectionMatrix * vec4(Vert, 1.0);\n" + " FogFragCoord = gl_Position.w;\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D Tex;\n" + "uniform sampler2D LMTex;\n" + "uniform sampler2D FullbrightTex;\n" + "uniform bool UseFullbrightTex;\n" + "uniform bool UseOverbright;\n" + "uniform bool UseAlphaTest;\n" + "uniform float Alpha;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " vec4 result = texture2D(Tex, gl_TexCoord[0].xy);\n" + " if (UseAlphaTest && (result.a < 0.666))\n" + " discard;\n" + " result *= texture2D(LMTex, gl_TexCoord[1].xy);\n" + " if (UseOverbright)\n" + " result.rgb *= 2.0;\n" + " if (UseFullbrightTex)\n" + " result += texture2D(FullbrightTex, gl_TexCoord[0].xy);\n" + " result = clamp(result, 0.0, 1.0);\n" + " float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n" + " fog = clamp(fog, 0.0, 1.0);\n" + " result = mix(gl_Fog.color, result, fog);\n" + " result.a = Alpha;\n" // FIXME: This will make almost transparent things cut holes though heavy fog + " gl_FragColor = result;\n" + "}\n"; + + if (!gl_glsl_alias_able) + return; + + r_world_program = GL_CreateProgram (vertSource, fragSource, sizeof(bindings)/sizeof(bindings[0]), bindings); + + if (r_world_program != 0) + { + // get uniform locations + texLoc = GL_GetUniformLocation (&r_world_program, "Tex"); + LMTexLoc = GL_GetUniformLocation (&r_world_program, "LMTex"); + fullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "FullbrightTex"); + useFullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "UseFullbrightTex"); + useOverbrightLoc = GL_GetUniformLocation (&r_world_program, "UseOverbright"); + useAlphaTestLoc = GL_GetUniformLocation (&r_world_program, "UseAlphaTest"); + alphaLoc = GL_GetUniformLocation (&r_world_program, "Alpha"); + } +} + +extern GLuint gl_bmodel_vbo; + +/* +================ +R_DrawTextureChains_GLSL -- ericw + +Draw lightmapped surfaces with fulbrights in one pass, using VBO. +Requires 3 TMUs, OpenGL 2.0 +================ +*/ +void R_DrawTextureChains_GLSL (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + int lastlightmap; + gltexture_t *fullbright = NULL; + float entalpha; + + entalpha = (ent != NULL) ? ENTALPHA_DECODE(ent->alpha) : 1.0f; + +// enable blending / disable depth writes + if (entalpha < 1) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + } + + GL_UseProgramFunc (r_world_program); + +// Bind the buffers + GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory! + + GL_EnableVertexAttribArrayFunc (vertAttrIndex); + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex); + + GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0)); + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3); + GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5); + +// set uniforms + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (LMTexLoc, 1); + GL_Uniform1iFunc (fullbrightTexLoc, 2); + GL_Uniform1iFunc (useFullbrightTexLoc, 0); + GL_Uniform1iFunc (useOverbrightLoc, (int)gl_overbright.value); + GL_Uniform1iFunc (useAlphaTestLoc, 0); + GL_Uniform1fFunc (alphaLoc, entalpha); + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTILED | SURF_NOTEXTURE)) + continue; + + // Enable/disable TMU 2 (fullbrights) + // FIXME: Move below to where we bind GL_TEXTURE0 + if (gl_fullbrights.value && (fullbright = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright)) + { + GL_SelectTexture (GL_TEXTURE2); + GL_Bind (fullbright); + GL_Uniform1iFunc (useFullbrightTexLoc, 1); + } + else + GL_Uniform1iFunc (useFullbrightTexLoc, 0); + + R_ClearBatch (); + + bound = false; + lastlightmap = 0; // avoid compiler warning + for (s = t->texturechains[chain]; s; s = s->texturechain) + if (!s->culled) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_SelectTexture (GL_TEXTURE0); + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + GL_Uniform1iFunc (useAlphaTestLoc, 1); // Flip alpha test back on + + bound = true; + lastlightmap = s->lightmaptexturenum; + } + + if (s->lightmaptexturenum != lastlightmap) + R_FlushBatch (); + + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (lightmap_textures[s->lightmaptexturenum]); + lastlightmap = s->lightmaptexturenum; + R_BatchSurface (s); + + rs_brushpasses++; + } + + R_FlushBatch (); + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + GL_Uniform1iFunc (useAlphaTestLoc, 0); // Flip alpha test back off + } + + // clean up + GL_DisableVertexAttribArrayFunc (vertAttrIndex); + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex); + + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + + if (entalpha < 1) + { + glDepthMask (GL_TRUE); + glDisable (GL_BLEND); + } +} + +/* +============= +R_DrawWorld -- johnfitz -- rewritten +============= +*/ +void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + float entalpha; + + if (ent != NULL) + entalpha = ENTALPHA_DECODE(ent->alpha); + else + entalpha = 1; + +// ericw -- the mh dynamic lightmap speedup: make a first pass through all +// surfaces we are going to draw, and rebuild any lightmaps that need it. +// this also chains surfaces by lightmap which is used by r_lightmap 1. +// the previous implementation of the speedup uploaded lightmaps one frame +// late which was visible under some conditions, this method avoids that. + R_BuildLightmapChains (model, chain); + R_UploadLightmaps (); + + if (r_drawflat_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + R_DrawTextureChains_Drawflat (model, chain); + glEnable (GL_TEXTURE_2D); + return; + } + + if (r_fullbright_cheatsafe) + { + R_BeginTransparentDrawing (entalpha); + R_DrawTextureChains_TextureOnly (model, ent, chain); + R_EndTransparentDrawing (entalpha); + goto fullbrights; + } + + if (r_lightmap_cheatsafe) + { + if (!gl_overbright.value) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0.5, 0.5, 0.5); + } + R_DrawLightmapChains (); + if (!gl_overbright.value) + { + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + R_DrawTextureChains_White (model, chain); + return; + } + + R_BeginTransparentDrawing (entalpha); + + R_DrawTextureChains_NoTexture (model, chain); + + // OpenGL 2 fast path + if (r_world_program != 0) + { + R_EndTransparentDrawing (entalpha); + + R_DrawTextureChains_GLSL (model, ent, chain); + return; + } + + if (gl_overbright.value) + { + if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners + { + GL_EnableMultitexture (); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_DisableMultitexture (); + R_DrawTextureChains_Multitexture (model, ent, chain); + GL_EnableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DisableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (entalpha < 1) //case 2: can't do multipass if entity has alpha, so just draw the texture + { + R_DrawTextureChains_TextureOnly (model, ent, chain); + } + else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass + { + //to make fog work with multipass lightmapping, need to do one pass + //with no fog, one modulate pass with black fog, and one additive + //pass with black geometry and normal fog + Fog_DisableGFog (); + R_DrawTextureChains_TextureOnly (model, ent, chain); + Fog_EnableGFog (); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR); //2x modulate + Fog_StartAdditive (); + R_DrawLightmapChains (); + Fog_StopAdditive (); + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + R_DrawTextureChains_TextureOnly (model, ent, chain); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } + } + else + { + if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation + { + GL_EnableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DisableMultitexture (); + R_DrawTextureChains_Multitexture (model, ent, chain); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (entalpha < 1) //case 5: can't do multipass if entity has alpha, so just draw the texture + { + R_DrawTextureChains_TextureOnly (model, ent, chain); + } + else //case 6: texture in one pass, lightmap in a second pass, fog in third pass + { + //to make fog work with multipass lightmapping, need to do one pass + //with no fog, one modulate pass with black fog, and one additive + //pass with black geometry and normal fog + Fog_DisableGFog (); + R_DrawTextureChains_TextureOnly (model, ent, chain); + Fog_EnableGFog (); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); //modulate + Fog_StartAdditive (); + R_DrawLightmapChains (); + Fog_StopAdditive (); + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + R_DrawTextureChains_TextureOnly (model, ent, chain); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } + } + + R_EndTransparentDrawing (entalpha); + +fullbrights: + if (gl_fullbrights.value) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f (entalpha, entalpha, entalpha); + Fog_StartAdditive (); + R_DrawTextureChains_Glow (model, ent, chain); + Fog_StopAdditive (); + glColor3f (1, 1, 1); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } +} + +/* +============= +R_DrawWorld -- ericw -- moved from R_DrawTextureChains, which is no longer specific to the world. +============= +*/ +void R_DrawWorld (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains (cl.worldmodel, NULL, chain_world); +} + +/* +============= +R_DrawWorld_Water -- ericw -- moved from R_DrawTextureChains_Water, which is no longer specific to the world. +============= +*/ +void R_DrawWorld_Water (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains_Water (cl.worldmodel, NULL, chain_world); +} + +/* +============= +R_DrawWorld_ShowTris -- ericw -- moved from R_DrawTextureChains_ShowTris, which is no longer specific to the world. +============= +*/ +void R_DrawWorld_ShowTris (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains_ShowTris (cl.worldmodel, chain_world); +} diff --git a/source/render.h b/source/render.h new file mode 100644 index 0000000..4ecb973 --- /dev/null +++ b/source/render.h @@ -0,0 +1,178 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_RENDER_H +#define _QUAKE_RENDER_H + +// refresh.h -- public interface to refresh functions + +#define MAXCLIPPLANES 11 + +#define TOP_RANGE 16 // soldier uniform colors +#define BOTTOM_RANGE 96 + +//============================================================================= + +typedef struct efrag_s +{ + struct efrag_s *leafnext; + struct entity_s *entity; +} efrag_t; + +//johnfitz -- for lerping +#define LERP_MOVESTEP (1<<0) //this is a MOVETYPE_STEP entity, enable movement lerp +#define LERP_RESETANIM (1<<1) //disable anim lerping until next anim frame +#define LERP_RESETANIM2 (1<<2) //set this and previous flag to disable anim lerping for two anim frames +#define LERP_RESETMOVE (1<<3) //disable movement lerping until next origin/angles change +#define LERP_FINISH (1<<4) //use lerpfinish time from server update instead of assuming interval of 0.1 +//johnfitz + +typedef struct entity_s +{ + qboolean forcelink; // model changed + + int update_type; + + entity_state_t baseline; // to fill in defaults in updates + + double msgtime; // time of last update + vec3_t msg_origins[2]; // last two updates (0 is newest) + vec3_t origin; + vec3_t msg_angles[2]; // last two updates (0 is newest) + vec3_t angles; + struct qmodel_s *model; // NULL = no model + struct efrag_s *efrag; // linked list of efrags + int frame; + float syncbase; // for client-side animations + byte *colormap; + int effects; // light, particles, etc + int skinnum; // for Alias models + int visframe; // last frame this entity was + // found in an active leaf + + int dlightframe; // dynamic lighting + int dlightbits; + +// FIXME: could turn these into a union + int trivial_accept; + struct mnode_s *topnode; // for bmodels, first world node + // that splits bmodel, or NULL if + // not split + + byte alpha; //johnfitz -- alpha + byte lerpflags; //johnfitz -- lerping + float lerpstart; //johnfitz -- animation lerping + float lerptime; //johnfitz -- animation lerping + float lerpfinish; //johnfitz -- lerping -- server sent us a more accurate interval, use it instead of 0.1 + short previouspose; //johnfitz -- animation lerping + short currentpose; //johnfitz -- animation lerping +// short futurepose; //johnfitz -- animation lerping + float movelerpstart; //johnfitz -- transform lerping + vec3_t previousorigin; //johnfitz -- transform lerping + vec3_t currentorigin; //johnfitz -- transform lerping + vec3_t previousangles; //johnfitz -- transform lerping + vec3_t currentangles; //johnfitz -- transform lerping +} entity_t; + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + vrect_t vrect; // subwindow in video for refresh + // FIXME: not need vrect next field here? + vrect_t aliasvrect; // scaled Alias version + int vrectright, vrectbottom; // right & bottom screen coords + int aliasvrectright, aliasvrectbottom; // scaled Alias versions + float vrectrightedge; // rightmost right edge we care about, + // for use in edge list + float fvrectx, fvrecty; // for floating-point compares + float fvrectx_adj, fvrecty_adj; // left and top edges, for clamping + int vrect_x_adj_shift20; // (vrect.x + 0.5 - epsilon) << 20 + int vrectright_adj_shift20; // (vrectright + 0.5 - epsilon) << 20 + float fvrectright_adj, fvrectbottom_adj; + // right and bottom edges, for clamping + float fvrectright; // rightmost edge, for Alias clamping + float fvrectbottom; // bottommost edge, for Alias clamping + float horizontalFieldOfView; // at Z = 1.0, this many X is visible + // 2.0 = 90 degrees + float xOrigin; // should probably allways be 0.5 + float yOrigin; // between be around 0.3 to 0.5 + + vec3_t vieworg; + vec3_t viewangles; + + float fov_x, fov_y; + + int ambientlight; +} refdef_t; + + +// +// refresh +// +extern int reinit_surfcache; + + +extern refdef_t r_refdef; +extern vec3_t r_origin, vpn, vright, vup; + + +void R_Init (void); +void R_InitTextures (void); +void R_InitEfrags (void); +void R_RenderView (void); // must set r_refdef first +void R_ViewChanged (vrect_t *pvrect, int lineadj, float aspect); + // called whenever r_refdef or vid change +//void R_InitSky (struct texture_s *mt); // called at level load + +void R_CheckEfrags (void); //johnfitz +void R_AddEfrags (entity_t *ent); + +void R_NewMap (void); + + +void R_ParseParticleEffect (void); +void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count); +void R_RocketTrail (vec3_t start, vec3_t end, int type); +void R_EntityParticles (entity_t *ent); +void R_BlobExplosion (vec3_t org); +void R_ParticleExplosion (vec3_t org); +void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength); +void R_LavaSplash (vec3_t org); +void R_TeleportSplash (vec3_t org); + +void R_PushDlights (void); + + +// +// surface cache related +// +extern int reinit_surfcache; // if 1, surface cache is currently empty and +extern qboolean r_cache_thrash; // set if thrashing the surface cache + +int D_SurfaceCacheForRes (int width, int height); +void D_FlushCaches (void); +void D_DeleteSurfaceCache (void); +void D_InitCaches (void *buffer, int size); +void R_SetVrect (vrect_t *pvrect, vrect_t *pvrectin, int lineadj); + +#endif /* _QUAKE_RENDER_H */ + diff --git a/source/resource.h b/source/resource.h new file mode 100644 index 0000000..d31f0fa --- /dev/null +++ b/source/resource.h @@ -0,0 +1,26 @@ +#ifndef _QUAKE_RESOURCE_H +#define _QUAKE_RESOURCE_H + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by winquake.rc +// +#define IDS_STRING1 1 +#define IDI_ICON2 1 +#define IDD_DIALOG1 108 +#define IDD_PROGRESS 109 +#define IDC_PROGRESS 1000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 113 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif + +#endif /* _QUAKE_RESOURCE_H */ + diff --git a/source/sbar.c b/source/sbar.c new file mode 100644 index 0000000..9c68780 --- /dev/null +++ b/source/sbar.c @@ -0,0 +1,1312 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +int sb_updates; // if >= vid.numpages, no update needed + +#define STAT_MINUS 10 // num frame for '-' stats digit + +qpic_t *sb_nums[2][11]; +qpic_t *sb_colon, *sb_slash; +qpic_t *sb_ibar; +qpic_t *sb_sbar; +qpic_t *sb_scorebar; + +qpic_t *sb_weapons[7][8]; // 0 is active, 1 is owned, 2-5 are flashes +qpic_t *sb_ammo[4]; +qpic_t *sb_sigil[4]; +qpic_t *sb_armor[3]; +qpic_t *sb_items[32]; + +qpic_t *sb_faces[7][2]; // 0 is gibbed, 1 is dead, 2-6 are alive + // 0 is static, 1 is temporary animation +qpic_t *sb_face_invis; +qpic_t *sb_face_quad; +qpic_t *sb_face_invuln; +qpic_t *sb_face_invis_invuln; + +qboolean sb_showscores; + +int sb_lines; // scan lines to draw + +qpic_t *rsb_invbar[2]; +qpic_t *rsb_weapons[5]; +qpic_t *rsb_items[2]; +qpic_t *rsb_ammo[3]; +qpic_t *rsb_teambord; // PGM 01/19/97 - team color border + +//MED 01/04/97 added two more weapons + 3 alternates for grenade launcher +qpic_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 +qpic_t *hsb_items[2]; + +void Sbar_MiniDeathmatchOverlay (void); +void Sbar_DeathmatchOverlay (void); +void M_DrawPic (int x, int y, qpic_t *pic); + +/* +=============== +Sbar_ShowScores + +Tab key down +=============== +*/ +void Sbar_ShowScores (void) +{ + if (sb_showscores) + return; + sb_showscores = true; + sb_updates = 0; +} + +/* +=============== +Sbar_DontShowScores + +Tab key up +=============== +*/ +void Sbar_DontShowScores (void) +{ + sb_showscores = false; + sb_updates = 0; +} + +/* +=============== +Sbar_Changed +=============== +*/ +void Sbar_Changed (void) +{ + sb_updates = 0; // update next frame +} + +/* +=============== +Sbar_LoadPics -- johnfitz -- load all the sbar pics +=============== +*/ +void Sbar_LoadPics (void) +{ + int i; + + for (i = 0; i < 10; i++) + { + sb_nums[0][i] = Draw_PicFromWad (va("num_%i",i)); + sb_nums[1][i] = Draw_PicFromWad (va("anum_%i",i)); + } + + sb_nums[0][10] = Draw_PicFromWad ("num_minus"); + sb_nums[1][10] = Draw_PicFromWad ("anum_minus"); + + sb_colon = Draw_PicFromWad ("num_colon"); + sb_slash = Draw_PicFromWad ("num_slash"); + + sb_weapons[0][0] = Draw_PicFromWad ("inv_shotgun"); + sb_weapons[0][1] = Draw_PicFromWad ("inv_sshotgun"); + sb_weapons[0][2] = Draw_PicFromWad ("inv_nailgun"); + sb_weapons[0][3] = Draw_PicFromWad ("inv_snailgun"); + sb_weapons[0][4] = Draw_PicFromWad ("inv_rlaunch"); + sb_weapons[0][5] = Draw_PicFromWad ("inv_srlaunch"); + sb_weapons[0][6] = Draw_PicFromWad ("inv_lightng"); + + sb_weapons[1][0] = Draw_PicFromWad ("inv2_shotgun"); + sb_weapons[1][1] = Draw_PicFromWad ("inv2_sshotgun"); + sb_weapons[1][2] = Draw_PicFromWad ("inv2_nailgun"); + sb_weapons[1][3] = Draw_PicFromWad ("inv2_snailgun"); + sb_weapons[1][4] = Draw_PicFromWad ("inv2_rlaunch"); + sb_weapons[1][5] = Draw_PicFromWad ("inv2_srlaunch"); + sb_weapons[1][6] = Draw_PicFromWad ("inv2_lightng"); + + for (i = 0; i < 5; i++) + { + sb_weapons[2+i][0] = Draw_PicFromWad (va("inva%i_shotgun",i+1)); + sb_weapons[2+i][1] = Draw_PicFromWad (va("inva%i_sshotgun",i+1)); + sb_weapons[2+i][2] = Draw_PicFromWad (va("inva%i_nailgun",i+1)); + sb_weapons[2+i][3] = Draw_PicFromWad (va("inva%i_snailgun",i+1)); + sb_weapons[2+i][4] = Draw_PicFromWad (va("inva%i_rlaunch",i+1)); + sb_weapons[2+i][5] = Draw_PicFromWad (va("inva%i_srlaunch",i+1)); + sb_weapons[2+i][6] = Draw_PicFromWad (va("inva%i_lightng",i+1)); + } + + sb_ammo[0] = Draw_PicFromWad ("sb_shells"); + sb_ammo[1] = Draw_PicFromWad ("sb_nails"); + sb_ammo[2] = Draw_PicFromWad ("sb_rocket"); + sb_ammo[3] = Draw_PicFromWad ("sb_cells"); + + sb_armor[0] = Draw_PicFromWad ("sb_armor1"); + sb_armor[1] = Draw_PicFromWad ("sb_armor2"); + sb_armor[2] = Draw_PicFromWad ("sb_armor3"); + + sb_items[0] = Draw_PicFromWad ("sb_key1"); + sb_items[1] = Draw_PicFromWad ("sb_key2"); + sb_items[2] = Draw_PicFromWad ("sb_invis"); + sb_items[3] = Draw_PicFromWad ("sb_invuln"); + sb_items[4] = Draw_PicFromWad ("sb_suit"); + sb_items[5] = Draw_PicFromWad ("sb_quad"); + + sb_sigil[0] = Draw_PicFromWad ("sb_sigil1"); + sb_sigil[1] = Draw_PicFromWad ("sb_sigil2"); + sb_sigil[2] = Draw_PicFromWad ("sb_sigil3"); + sb_sigil[3] = Draw_PicFromWad ("sb_sigil4"); + + sb_faces[4][0] = Draw_PicFromWad ("face1"); + sb_faces[4][1] = Draw_PicFromWad ("face_p1"); + sb_faces[3][0] = Draw_PicFromWad ("face2"); + sb_faces[3][1] = Draw_PicFromWad ("face_p2"); + sb_faces[2][0] = Draw_PicFromWad ("face3"); + sb_faces[2][1] = Draw_PicFromWad ("face_p3"); + sb_faces[1][0] = Draw_PicFromWad ("face4"); + sb_faces[1][1] = Draw_PicFromWad ("face_p4"); + sb_faces[0][0] = Draw_PicFromWad ("face5"); + sb_faces[0][1] = Draw_PicFromWad ("face_p5"); + + sb_face_invis = Draw_PicFromWad ("face_invis"); + sb_face_invuln = Draw_PicFromWad ("face_invul2"); + sb_face_invis_invuln = Draw_PicFromWad ("face_inv2"); + sb_face_quad = Draw_PicFromWad ("face_quad"); + + sb_sbar = Draw_PicFromWad ("sbar"); + sb_ibar = Draw_PicFromWad ("ibar"); + sb_scorebar = Draw_PicFromWad ("scorebar"); + +//MED 01/04/97 added new hipnotic weapons + if (hipnotic) + { + hsb_weapons[0][0] = Draw_PicFromWad ("inv_laser"); + hsb_weapons[0][1] = Draw_PicFromWad ("inv_mjolnir"); + hsb_weapons[0][2] = Draw_PicFromWad ("inv_gren_prox"); + hsb_weapons[0][3] = Draw_PicFromWad ("inv_prox_gren"); + hsb_weapons[0][4] = Draw_PicFromWad ("inv_prox"); + + hsb_weapons[1][0] = Draw_PicFromWad ("inv2_laser"); + hsb_weapons[1][1] = Draw_PicFromWad ("inv2_mjolnir"); + hsb_weapons[1][2] = Draw_PicFromWad ("inv2_gren_prox"); + hsb_weapons[1][3] = Draw_PicFromWad ("inv2_prox_gren"); + hsb_weapons[1][4] = Draw_PicFromWad ("inv2_prox"); + + for (i = 0; i < 5; i++) + { + hsb_weapons[2+i][0] = Draw_PicFromWad (va("inva%i_laser",i+1)); + hsb_weapons[2+i][1] = Draw_PicFromWad (va("inva%i_mjolnir",i+1)); + hsb_weapons[2+i][2] = Draw_PicFromWad (va("inva%i_gren_prox",i+1)); + hsb_weapons[2+i][3] = Draw_PicFromWad (va("inva%i_prox_gren",i+1)); + hsb_weapons[2+i][4] = Draw_PicFromWad (va("inva%i_prox",i+1)); + } + + hsb_items[0] = Draw_PicFromWad ("sb_wsuit"); + hsb_items[1] = Draw_PicFromWad ("sb_eshld"); + } + + if (rogue) + { + rsb_invbar[0] = Draw_PicFromWad ("r_invbar1"); + rsb_invbar[1] = Draw_PicFromWad ("r_invbar2"); + + rsb_weapons[0] = Draw_PicFromWad ("r_lava"); + rsb_weapons[1] = Draw_PicFromWad ("r_superlava"); + rsb_weapons[2] = Draw_PicFromWad ("r_gren"); + rsb_weapons[3] = Draw_PicFromWad ("r_multirock"); + rsb_weapons[4] = Draw_PicFromWad ("r_plasma"); + + rsb_items[0] = Draw_PicFromWad ("r_shield1"); + rsb_items[1] = Draw_PicFromWad ("r_agrav1"); + +// PGM 01/19/97 - team color border + rsb_teambord = Draw_PicFromWad ("r_teambord"); +// PGM 01/19/97 - team color border + + rsb_ammo[0] = Draw_PicFromWad ("r_ammolava"); + rsb_ammo[1] = Draw_PicFromWad ("r_ammomulti"); + rsb_ammo[2] = Draw_PicFromWad ("r_ammoplasma"); + } +} + +/* +=============== +Sbar_Init -- johnfitz -- rewritten +=============== +*/ +void Sbar_Init (void) +{ + Cmd_AddCommand ("+showscores", Sbar_ShowScores); + Cmd_AddCommand ("-showscores", Sbar_DontShowScores); + + Sbar_LoadPics (); +} + + +//============================================================================= + +// drawing routines are relative to the status bar location + +/* +============= +Sbar_DrawPic -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +============= +*/ +void Sbar_DrawPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y + 24, pic); +} + +/* +============= +Sbar_DrawPicAlpha -- johnfitz +============= +*/ +void Sbar_DrawPicAlpha (int x, int y, qpic_t *pic, float alpha) +{ + glDisable (GL_ALPHA_TEST); + glEnable (GL_BLEND); + glColor4f(1,1,1,alpha); + Draw_Pic (x, y + 24, pic); + glColor4f(1,1,1,1); // ericw -- changed from glColor3f to work around intel 855 bug with "r_oldwater 0" and "scr_sbaralpha 0" + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); +} + +/* +================ +Sbar_DrawCharacter -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +================ +*/ +void Sbar_DrawCharacter (int x, int y, int num) +{ + Draw_Character (x, y + 24, num); +} + +/* +================ +Sbar_DrawString -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +================ +*/ +void Sbar_DrawString (int x, int y, const char *str) +{ + Draw_String (x, y + 24, str); +} + +/* +=============== +Sbar_DrawScrollString -- johnfitz + +scroll the string inside a glscissor region +=============== +*/ +void Sbar_DrawScrollString (int x, int y, int width, const char *str) +{ + float scale; + int len, ofs, left; + + scale = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + left = x * scale; + if (cl.gametype != GAME_DEATHMATCH) + left += (((float)glwidth - 320.0 * scale) / 2); + + glEnable (GL_SCISSOR_TEST); + glScissor (left, 0, width * scale, glheight); + + len = strlen(str)*8 + 40; + ofs = ((int)(realtime*30))%len; + Sbar_DrawString (x - ofs, y, str); + Sbar_DrawCharacter (x - ofs + len - 32, y, '/'); + Sbar_DrawCharacter (x - ofs + len - 24, y, '/'); + Sbar_DrawCharacter (x - ofs + len - 16, y, '/'); + Sbar_DrawString (x - ofs + len, y, str); + + glDisable (GL_SCISSOR_TEST); +} + +/* +============= +Sbar_itoa +============= +*/ +int Sbar_itoa (int num, char *buf) +{ + char *str; + int pow10; + int dig; + + str = buf; + + if (num < 0) + { + *str++ = '-'; + num = -num; + } + + for (pow10 = 10 ; num >= pow10 ; pow10 *= 10) + ; + + do + { + pow10 /= 10; + dig = num/pow10; + *str++ = '0'+dig; + num -= dig*pow10; + } while (pow10 != 1); + + *str = 0; + + return str-buf; +} + + +/* +============= +Sbar_DrawNum +============= +*/ +void Sbar_DrawNum (int x, int y, int num, int digits, int color) +{ + char str[12]; + char *ptr; + int l, frame; + + num = q_min(999,num); //johnfitz -- cap high values rather than truncating number + + l = Sbar_itoa (num, str); + 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]); //johnfitz -- DrawTransPic is obsolete + x += 24; + ptr++; + } +} + +//============================================================================= + +int fragsort[MAX_SCOREBOARD]; + +char scoreboardtext[MAX_SCOREBOARD][20]; +int scoreboardtop[MAX_SCOREBOARD]; +int scoreboardbottom[MAX_SCOREBOARD]; +int scoreboardcount[MAX_SCOREBOARD]; +int scoreboardlines; + +/* +=============== +Sbar_SortFrags +=============== +*/ +void Sbar_SortFrags (void) +{ + int i, j, k; + +// sort by frags + scoreboardlines = 0; + for (i = 0; i < cl.maxclients; i++) + { + if (cl.scores[i].name[0]) + { + fragsort[scoreboardlines] = i; + scoreboardlines++; + } + } + + for (i = 0; i < scoreboardlines; i++) + { + for (j = 0; j < scoreboardlines - 1 - i; j++) + { + if (cl.scores[fragsort[j]].frags < cl.scores[fragsort[j+1]].frags) + { + k = fragsort[j]; + fragsort[j] = fragsort[j+1]; + fragsort[j+1] = k; + } + } + } +} + +int Sbar_ColorForMap (int m) +{ + return m < 128 ? m + 8 : m + 8; +} + +/* +=============== +Sbar_UpdateScoreboard +=============== +*/ +void Sbar_UpdateScoreboard (void) +{ + int i, k; + int top, bottom; + scoreboard_t *s; + + Sbar_SortFrags (); + +// draw the text + memset (scoreboardtext, 0, sizeof(scoreboardtext)); + + for (i = 0; i < scoreboardlines; i++) + { + k = fragsort[i]; + s = &cl.scores[k]; + sprintf (&scoreboardtext[i][1], "%3i %s", s->frags, s->name); + + top = s->colors & 0xf0; + bottom = (s->colors & 15) <<4; + scoreboardtop[i] = Sbar_ColorForMap (top); + scoreboardbottom[i] = Sbar_ColorForMap (bottom); + } +} + +/* +=============== +Sbar_SoloScoreboard -- johnfitz -- new layout +=============== +*/ +void Sbar_SoloScoreboard (void) +{ + char str[256]; + int minutes, seconds, tens, units; + int len; + + sprintf (str,"Kills: %i/%i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + Sbar_DrawString (8, 12, str); + + sprintf (str,"Secrets: %i/%i", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); + Sbar_DrawString (312 - strlen(str)*8, 12, str); + + if (!fitzmode) + { /* QuakeSpasm customization: */ + q_snprintf (str, sizeof(str), "skill %i", (int)(skill.value + 0.5)); + Sbar_DrawString (160 - strlen(str)*4, 12, str); + + q_snprintf (str, sizeof(str), "%s (%s)", cl.levelname, cl.mapname); + len = strlen (str); + if (len > 40) + Sbar_DrawScrollString (0, 4, 320, str); + else + Sbar_DrawString (160 - len*4, 4, str); + return; + } + minutes = cl.time / 60; + seconds = cl.time - 60*minutes; + tens = seconds / 10; + units = seconds - 10*tens; + sprintf (str,"%i:%i%i", minutes, tens, units); + Sbar_DrawString (160 - strlen(str)*4, 12, str); + + len = strlen (cl.levelname); + if (len > 40) + Sbar_DrawScrollString (0, 4, 320, cl.levelname); + else + Sbar_DrawString (160 - len*4, 4, cl.levelname); +} + +/* +=============== +Sbar_DrawScoreboard +=============== +*/ +void Sbar_DrawScoreboard (void) +{ + Sbar_SoloScoreboard (); + if (cl.gametype == GAME_DEATHMATCH) + Sbar_DeathmatchOverlay (); +} + +//============================================================================= + +/* +=============== +Sbar_DrawInventory +=============== +*/ +void Sbar_DrawInventory (void) +{ + int i, val; + char num[6]; + float time; + int flashon; + + if (rogue) + { + if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) + Sbar_DrawPicAlpha (0, -24, rsb_invbar[0], scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + else + Sbar_DrawPicAlpha (0, -24, rsb_invbar[1], scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + } + else + { + Sbar_DrawPicAlpha (0, -24, sb_ibar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + } + +// weapons + for (i = 0; i < 7; i++) + { + if (cl.items & (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN< 1) + sb_updates = 0; // force update to remove flash + } + } + +// MED 01/04/97 +// hipnotic weapons + if (hipnotic) + { + int grenadeflashing = 0; + for (i = 0; i < 4; i++) + { + if (cl.items & (1<= 10) + { + if (cl.stats[STAT_ACTIVEWEAPON] == (1< 1) + sb_updates = 0; // force update to remove flash + } + } + } + + if (rogue) + { + // check for powered up weapon. + if ( cl.stats[STAT_ACTIVEWEAPON] >= 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++) + { + val = cl.stats[STAT_SHELLS+i]; + val = (val < 0)? 0 : q_min(999,val);//johnfitz -- cap displayed value to 999 + sprintf (num, "%3i", val); + if (num[0] != ' ') + Sbar_DrawCharacter ( (6*i+1)*8 + 2, -24, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter ( (6*i+2)*8 + 2, -24, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter ( (6*i+3)*8 + 2, -24, 18 + num[2] - '0'); + } + + flashon = 0; + // items + for (i = 0; i < 6; i++) + { + if (cl.items & (1<<(17+i))) + { + time = cl.item_gettime[17+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + { + //MED 01/04/97 changed keys + if (!hipnotic || (i > 1)) + { + Sbar_DrawPic (192 + i*16, -16, sb_items[i]); + } + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + //MED 01/04/97 added hipnotic items + // hipnotic items + if (hipnotic) + { + for (i = 0; i < 2; i++) + { + if (cl.items & (1<<(24+i))) + { + time = cl.item_gettime[24+i]; + if (time && time > cl.time - 2 && flashon ) + { // flash frame + sb_updates = 0; + } + else + { + Sbar_DrawPic (288 + i*16, -16, hsb_items[i]); + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } + + if (rogue) + { + // new rogue items + for (i = 0; i < 2; i++) + { + if (cl.items & (1<<(29+i))) + { + time = cl.item_gettime[29+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + { + Sbar_DrawPic (288 + i*16, -16, rsb_items[i]); + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } + else + { + // sigils + for (i = 0; i < 4; i++) + { + if (cl.items & (1<<(28+i))) + { + time = cl.item_gettime[28+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + Sbar_DrawPic (320-32 + i*8, -16, sb_sigil[i]); + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } +} + +//============================================================================= + +/* +=============== +Sbar_DrawFrags -- johnfitz -- heavy revision +=============== +*/ +void Sbar_DrawFrags (void) +{ + int numscores, i, x, color; + char num[12]; + scoreboard_t *s; + + Sbar_SortFrags (); + +// draw the text + numscores = q_min(scoreboardlines, 4); + + for (i = 0, x = 184; iname[0]) + continue; + + // top color + color = s->colors & 0xf0; + color = Sbar_ColorForMap (color); + Draw_Fill (x + 10, 1, 28, 4, color, 1); + + // bottom color + color = (s->colors & 15)<<4; + color = Sbar_ColorForMap (color); + Draw_Fill (x + 10, 5, 28, 3, color, 1); + + // number + sprintf (num, "%3i", s->frags); + Sbar_DrawCharacter (x + 12, -24, num[0]); + Sbar_DrawCharacter (x + 20, -24, num[1]); + Sbar_DrawCharacter (x + 28, -24, num[2]); + + // brackets + if (fragsort[i] == cl.viewentity - 1) + { + Sbar_DrawCharacter (x + 6, -24, 16); + Sbar_DrawCharacter (x + 32, -24, 17); + } + } +} + +//============================================================================= + + +/* +=============== +Sbar_DrawFace +=============== +*/ +void Sbar_DrawFace (void) +{ + int f, anim; + +// PGM 01/19/97 - team color drawing +// PGM 03/02/97 - fixed so color swatch only appears in CTF modes + if (rogue && (cl.maxclients != 1) && (teamplay.value>3) && (teamplay.value<7)) + { + int top, bottom; + int xofs; + char num[12]; + scoreboard_t *s; + + s = &cl.scores[cl.viewentity - 1]; + // draw background + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + if (cl.gametype == GAME_DEATHMATCH) + xofs = 113; + else + xofs = ((vid.width - 320)>>1) + 113; + + Sbar_DrawPic (112, 0, rsb_teambord); + Draw_Fill (xofs, /*vid.height-*/24+3, 22, 9, top, 1); //johnfitz -- sbar coords are now relative + Draw_Fill (xofs, /*vid.height-*/24+12, 22, 9, bottom, 1); //johnfitz -- sbar coords are now relative + + // draw number + f = s->frags; + sprintf (num, "%3i",f); + + if (top == 8) + { + if (num[0] != ' ') + Sbar_DrawCharacter(113, 3, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter(120, 3, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter(127, 3, 18 + num[2] - '0'); + } + else + { + Sbar_DrawCharacter (113, 3, num[0]); + Sbar_DrawCharacter (120, 3, num[1]); + Sbar_DrawCharacter (127, 3, num[2]); + } + + return; + } +// PGM 01/19/97 - team color drawing + + if ((cl.items & (IT_INVISIBILITY | IT_INVULNERABILITY)) + == (IT_INVISIBILITY | IT_INVULNERABILITY)) + { + Sbar_DrawPic (112, 0, sb_face_invis_invuln); + return; + } + if (cl.items & IT_QUAD) + { + Sbar_DrawPic (112, 0, sb_face_quad ); + return; + } + if (cl.items & IT_INVISIBILITY) + { + Sbar_DrawPic (112, 0, sb_face_invis ); + return; + } + if (cl.items & IT_INVULNERABILITY) + { + Sbar_DrawPic (112, 0, sb_face_invuln); + return; + } + + if (cl.stats[STAT_HEALTH] >= 100) + f = 4; + else + f = cl.stats[STAT_HEALTH] / 20; + if (f < 0) // in case we ever decide to draw when health <= 0 + f = 0; + + if (cl.time <= cl.faceanimtime) + { + anim = 1; + sb_updates = 0; // make sure the anim gets drawn over + } + else + anim = 0; + Sbar_DrawPic (112, 0, sb_faces[f][anim]); +} + +/* +=============== +Sbar_Draw +=============== +*/ +void Sbar_Draw (void) +{ + float w; //johnfitz + + if (scr_con_current == vid.height) + return; // console is full screen + + if (cl.intermission) + return; //johnfitz -- never draw sbar during intermission + + if (sb_updates >= vid.numpages && !gl_clear.value && scr_sbaralpha.value >= 1 //johnfitz -- gl_clear, scr_sbaralpha + && !(gl_glsl_gamma_able && vid_gamma.value != 1)) //ericw -- must draw sbar every frame if doing glsl gamma + return; + + sb_updates++; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + //johnfitz -- don't waste fillrate by clearing the area behind the sbar + w = CLAMP (320.0f, scr_sbarscale.value * 320.0f, (float)glwidth); + if (sb_lines && glwidth > w) + { + if (scr_sbaralpha.value < 1) + Draw_TileClear (0, glheight - sb_lines, glwidth, sb_lines); + if (cl.gametype == GAME_DEATHMATCH) + Draw_TileClear (w, glheight - sb_lines, glwidth - w, sb_lines); + else + { + Draw_TileClear (0, glheight - sb_lines, (glwidth - w) / 2.0f, sb_lines); + Draw_TileClear ((glwidth - w) / 2.0f + w, glheight - sb_lines, (glwidth - w) / 2.0f, sb_lines); + } + } + //johnfitz + + GL_SetCanvas (CANVAS_SBAR); //johnfitz + + if (scr_viewsize.value < 110) //johnfitz -- check viewsize instead of sb_lines + { + Sbar_DrawInventory (); + if (cl.maxclients != 1) + Sbar_DrawFrags (); + } + + if (sb_showscores || cl.stats[STAT_HEALTH] <= 0) + { + Sbar_DrawPicAlpha (0, 0, sb_scorebar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + Sbar_DrawScoreboard (); + sb_updates = 0; + } + else if (scr_viewsize.value < 120) //johnfitz -- check viewsize instead of sb_lines + { + Sbar_DrawPicAlpha (0, 0, sb_sbar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + + // keys (hipnotic only) + //MED 01/04/97 moved keys here so they would not be overwritten + if (hipnotic) + { + if (cl.items & IT_KEY1) + Sbar_DrawPic (209, 3, sb_items[0]); + if (cl.items & IT_KEY2) + Sbar_DrawPic (209, 12, sb_items[1]); + } + // armor + if (cl.items & IT_INVULNERABILITY) + { + Sbar_DrawNum (24, 0, 666, 3, 1); + Sbar_DrawPic (0, 0, draw_disc); + } + else + { + if (rogue) + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, + cl.stats[STAT_ARMOR] <= 25); + if (cl.items & RIT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.items & RIT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.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.items & IT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.items & IT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.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 (rogue) + { + if (cl.items & RIT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.items & RIT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.items & RIT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.items & RIT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + else if (cl.items & RIT_LAVA_NAILS) + Sbar_DrawPic (224, 0, rsb_ammo[0]); + else if (cl.items & RIT_PLASMA_AMMO) + Sbar_DrawPic (224, 0, rsb_ammo[1]); + else if (cl.items & RIT_MULTI_ROCKETS) + Sbar_DrawPic (224, 0, rsb_ammo[2]); + } + else + { + if (cl.items & IT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.items & IT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.items & IT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.items & IT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + } + + Sbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, + cl.stats[STAT_AMMO] <= 10); + } + + //johnfitz -- removed the vid.width > 320 check here + if (cl.gametype == GAME_DEATHMATCH) + Sbar_MiniDeathmatchOverlay (); +} + +//============================================================================= + +/* +================== +Sbar_IntermissionNumber + +================== +*/ +void Sbar_IntermissionNumber (int x, int y, int num, int digits, int color) +{ + char str[12]; + char *ptr; + int l, frame; + + l = Sbar_itoa (num, str); + 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'; + + Draw_Pic (x,y,sb_nums[color][frame]); //johnfitz -- stretched menus + x += 24; + ptr++; + } +} + +/* +================== +Sbar_DeathmatchOverlay + +================== +*/ +void Sbar_DeathmatchOverlay (void) +{ + qpic_t *pic; + int i, k, l; + int top, bottom; + int x, y, f; + char num[12]; + scoreboard_t *s; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/ranking.lmp"); + M_DrawPic ((320-pic->width)/2, 8, pic); + +// scores + Sbar_SortFrags (); + +// draw the text + l = scoreboardlines; + + x = 80; //johnfitz -- simplified becuase some positioning is handled elsewhere + y = 40; + for (i = 0; i < l; i++) + { + k = fragsort[i]; + s = &cl.scores[k]; + if (!s->name[0]) + continue; + + // draw background + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + Draw_Fill ( x, y, 40, 4, top, 1); //johnfitz -- stretched overlays + Draw_Fill ( x, y+4, 40, 4, bottom, 1); //johnfitz -- stretched overlays + + // draw number + f = s->frags; + sprintf (num, "%3i",f); + + Draw_Character ( x+8 , y, num[0]); //johnfitz -- stretched overlays + Draw_Character ( x+16 , y, num[1]); //johnfitz -- stretched overlays + Draw_Character ( x+24 , y, num[2]); //johnfitz -- stretched overlays + + if (k == cl.viewentity - 1) + Draw_Character ( x - 8, y, 12); //johnfitz -- stretched overlays + +#if 0 +{ + int total; + int n, minutes, tens, units; + + // draw time + total = cl.completed_time - s->entertime; + minutes = (int)total/60; + n = total - minutes*60; + tens = n/10; + units = n%10; + + sprintf (num, "%3i:%i%i", minutes, tens, units); + + M_Print ( x+48 , y, num); //johnfitz -- was Draw_String, changed for stretched overlays +} +#endif + + // draw name + M_Print (x+64, y, s->name); //johnfitz -- was Draw_String, changed for stretched overlays + + y += 10; + } + + GL_SetCanvas (CANVAS_SBAR); //johnfitz +} + +/* +================== +Sbar_MiniDeathmatchOverlay +================== +*/ +void Sbar_MiniDeathmatchOverlay (void) +{ + int i, k, top, bottom, x, y, f, numlines; + char num[12]; + float scale; //johnfitz + scoreboard_t *s; + + scale = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); //johnfitz + + //MAX_SCOREBOARDNAME = 32, so total width for this overlay plus sbar is 632, but we can cut off some i guess + if (glwidth/scale < 512 || scr_viewsize.value >= 120) //johnfitz -- test should consider scr_sbarscale + return; + +// scores + Sbar_SortFrags (); + +// draw the text + numlines = (scr_viewsize.value >= 110) ? 3 : 6; //johnfitz + + //find us + for (i = 0; i < scoreboardlines; i++) + if (fragsort[i] == cl.viewentity - 1) + break; + if (i == scoreboardlines) // we're not there + i = 0; + else // figure out start + i = i - numlines/2; + if (i > scoreboardlines - numlines) + i = scoreboardlines - numlines; + if (i < 0) + i = 0; + + x = 324; + y = (scr_viewsize.value >= 110) ? 24 : 0; //johnfitz -- start at the right place + for ( ; i < scoreboardlines && y <= 48; i++, y+=8) //johnfitz -- change y init, test, inc + { + k = fragsort[i]; + s = &cl.scores[k]; + if (!s->name[0]) + continue; + + // colors + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + Draw_Fill (x, y+1, 40, 4, top, 1); + Draw_Fill (x, y+5, 40, 3, bottom, 1); + + // number + f = s->frags; + sprintf (num, "%3i",f); + Draw_Character (x+ 8, y, num[0]); + Draw_Character (x+16, y, num[1]); + Draw_Character (x+24, y, num[2]); + + // brackets + if (k == cl.viewentity - 1) + { + Draw_Character (x, y, 16); + Draw_Character (x+32, y, 17); + } + + // name + Draw_String (x+48, y, s->name); + } +} + +/* +================== +Sbar_IntermissionOverlay +================== +*/ +void Sbar_IntermissionOverlay (void) +{ + qpic_t *pic; + int dig; + int num; + + if (cl.gametype == GAME_DEATHMATCH) + { + Sbar_DeathmatchOverlay (); + return; + } + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/complete.lmp"); + Draw_Pic (64, 24, pic); + + pic = Draw_CachePic ("gfx/inter.lmp"); + Draw_Pic (0, 56, pic); + + dig = cl.completed_time/60; + Sbar_IntermissionNumber (152, 64, dig, 3, 0); //johnfitz -- was 160 + num = cl.completed_time - dig*60; + Draw_Pic (224,64,sb_colon); //johnfitz -- was 234 + Draw_Pic (240,64,sb_nums[0][num/10]); //johnfitz -- was 246 + Draw_Pic (264,64,sb_nums[0][num%10]); //johnfitz -- was 266 + + Sbar_IntermissionNumber (152, 104, cl.stats[STAT_SECRETS], 3, 0); //johnfitz -- was 160 + Draw_Pic (224,104,sb_slash); //johnfitz -- was 232 + Sbar_IntermissionNumber (240, 104, cl.stats[STAT_TOTALSECRETS], 3, 0); //johnfitz -- was 248 + + Sbar_IntermissionNumber (152, 144, cl.stats[STAT_MONSTERS], 3, 0); //johnfitz -- was 160 + Draw_Pic (224,144,sb_slash); //johnfitz -- was 232 + Sbar_IntermissionNumber (240, 144, cl.stats[STAT_TOTALMONSTERS], 3, 0); //johnfitz -- was 248 +} + + +/* +================== +Sbar_FinaleOverlay +================== +*/ +void Sbar_FinaleOverlay (void) +{ + qpic_t *pic; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/finale.lmp"); + Draw_Pic ( (320 - pic->width)/2, 16, pic); //johnfitz -- stretched menus +} + diff --git a/source/sbar.h b/source/sbar.h new file mode 100644 index 0000000..bce73b5 --- /dev/null +++ b/source/sbar.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_SBAR_H +#define _QUAKE_SBAR_H + +// the status bar is only redrawn if something has changed, but if anything +// does, the entire thing will be redrawn for the next vid.numpages frames. + +extern int sb_lines; // scan lines to draw + +void Sbar_Init (void); +void Sbar_LoadPics (void); + +void Sbar_Changed (void); +// call whenever any of the client stats represented on the sbar changes + +void Sbar_Draw (void); +// called every frame by screen + +void Sbar_IntermissionOverlay (void); +// called each frame after the level has been completed + +void Sbar_FinaleOverlay (void); + +#endif /* _QUAKE_SBAR_H */ + diff --git a/source/screen.h b/source/screen.h new file mode 100644 index 0000000..73c888c --- /dev/null +++ b/source/screen.h @@ -0,0 +1,84 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_SCREEN_H +#define _QUAKE_SCREEN_H + +// screen.h + +void SCR_Init (void); +void SCR_LoadPics (void); + +void SCR_UpdateScreen (void); + + +void SCR_SizeUp (void); +void SCR_SizeDown (void); +void SCR_BringDownConsole (void); +void SCR_CenterPrint (const char *str); + +void SCR_BeginLoadingPlaque (void); +void SCR_EndLoadingPlaque (void); + +int SCR_ModalMessage (const char *text, float timeout); //johnfitz -- added timeout + +extern float scr_con_current; +extern float scr_conlines; // lines of console to display + +extern int sb_lines; + +extern int clearnotify; // set to 0 whenever notify text is drawn +extern qboolean scr_disabled_for_loading; +extern qboolean scr_skipupdate; + +extern cvar_t scr_viewsize; + +extern cvar_t scr_sbaralpha; //johnfitz + +void SCR_UpdateWholeScreen (void); + +//johnfitz -- stuff for 2d drawing control +typedef enum { + CANVAS_NONE, + CANVAS_DEFAULT, + CANVAS_CONSOLE, + CANVAS_MENU, + CANVAS_SBAR, + CANVAS_WARPIMAGE, + CANVAS_CROSSHAIR, + CANVAS_BOTTOMLEFT, + CANVAS_BOTTOMRIGHT, + CANVAS_TOPRIGHT, + CANVAS_INVALID = -1 +} canvastype; +extern cvar_t scr_menuscale; +extern cvar_t scr_sbarscale; +extern cvar_t scr_conwidth; +extern cvar_t scr_conscale; +extern cvar_t scr_scale; +extern cvar_t scr_crosshairscale; +//johnfitz + +extern int scr_tileclear_updates; //johnfitz + +#endif /* _QUAKE_SCREEN_H */ + diff --git a/source/server.h b/source/server.h new file mode 100644 index 0000000..7eb3f51 --- /dev/null +++ b/source/server.h @@ -0,0 +1,232 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_SERVER_H +#define _QUAKE_SERVER_H + +// server.h + +typedef struct +{ + int maxclients; + int maxclientslimit; + struct client_s *clients; // [maxclients] + int serverflags; // episode completion information + qboolean changelevel_issued; // cleared when at SV_SpawnServer +} server_static_t; + +//============================================================================= + +typedef enum {ss_loading, ss_active} server_state_t; + +typedef struct +{ + qboolean active; // false if only a net client + + qboolean paused; + qboolean loadgame; // handle connections specially + + double time; + + int lastcheck; // used by PF_checkclient + double lastchecktime; + + char name[64]; // map name + char modelname[64]; // maps/.bsp, for model_precache[0] + struct qmodel_s *worldmodel; + const char *model_precache[MAX_MODELS]; // NULL terminated + struct qmodel_s *models[MAX_MODELS]; + const char *sound_precache[MAX_SOUNDS]; // NULL terminated + const char *lightstyles[MAX_LIGHTSTYLES]; + int num_edicts; + int max_edicts; + edict_t *edicts; // can NOT be array indexed, because + // edict_t is variable sized, but can + // be used to reference the world ent + server_state_t state; // some actions are only valid during load + + sizebuf_t datagram; + byte datagram_buf[MAX_DATAGRAM]; + + sizebuf_t reliable_datagram; // copied to all clients at end of frame + byte reliable_datagram_buf[MAX_DATAGRAM]; + + sizebuf_t signon; + byte signon_buf[MAX_MSGLEN-2]; //johnfitz -- was 8192, now uses MAX_MSGLEN + + unsigned protocol; //johnfitz + unsigned protocolflags; +} server_t; + + +#define NUM_PING_TIMES 16 +#define NUM_SPAWN_PARMS 16 + +typedef struct client_s +{ + qboolean active; // false = client is free + qboolean spawned; // false = don't send datagrams + qboolean dropasap; // has been told to go to another level + qboolean sendsignon; // only valid before spawned + + double last_message; // reliable messages must be sent + // periodically + + struct qsocket_s *netconnection; // communications handle + + usercmd_t cmd; // movement + vec3_t wishdir; // intended motion calced from cmd + + sizebuf_t message; // can be added to at any time, + // copied and clear once per frame + byte msgbuf[MAX_MSGLEN]; + edict_t *edict; // EDICT_NUM(clientnum+1) + char name[32]; // for printing to other people + int colors; + + float ping_times[NUM_PING_TIMES]; + int num_pings; // ping_times[num_pings%NUM_PING_TIMES] + +// spawn parms are carried from level to level + float spawn_parms[NUM_SPAWN_PARMS]; + +// client known data for deltas + int old_frags; +} 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 + +// 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 + +// 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_GLIMPSE 4 +#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 + +// entity effects + +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 + +#define SPAWNFLAG_NOT_EASY 256 +#define SPAWNFLAG_NOT_MEDIUM 512 +#define SPAWNFLAG_NOT_HARD 1024 +#define SPAWNFLAG_NOT_DEATHMATCH 2048 + +//============================================================================ + +extern cvar_t teamplay; +extern cvar_t skill; +extern cvar_t deathmatch; +extern cvar_t coop; +extern cvar_t fraglimit; +extern cvar_t timelimit; + +extern server_static_t svs; // persistant server info +extern server_t sv; // local server + +extern client_t *host_client; + +extern edict_t *sv_player; + +//=========================================================== + +void SV_Init (void); + +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); +void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume, + float attenuation); + +void SV_DropClient (qboolean crash); + +void SV_SendClientMessages (void); +void SV_ClearDatagram (void); + +int SV_ModelIndex (const char *name); + +void SV_SetIdealPitch (void); + +void SV_AddUpdates (void); + +void SV_ClientThink (void); +void SV_AddClientToServer (struct qsocket_s *ret); + +void SV_ClientPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void SV_BroadcastPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); + +void SV_Physics (void); + +qboolean SV_CheckBottom (edict_t *ent); +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink); + +void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg); + +void SV_MoveToGoal (void); + +void SV_CheckForNewClients (void); +void SV_RunClients (void); +void SV_SaveSpawnparms (); +void SV_SpawnServer (const char *server); + +#endif /* _QUAKE_SERVER_H */ + diff --git a/source/snd_codec.c b/source/snd_codec.c new file mode 100644 index 0000000..dc37fb6 --- /dev/null +++ b/source/snd_codec.c @@ -0,0 +1,318 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" +#include "snd_codec.h" +#include "snd_codeci.h" + +/* headers for individual codecs */ +#include "snd_mikmod.h" +#include "snd_xmp.h" +#include "snd_umx.h" +#include "snd_wave.h" +#include "snd_flac.h" +#include "snd_mp3.h" +#include "snd_vorbis.h" +#include "snd_opus.h" + + +static snd_codec_t *codecs; + +/* +================= +S_CodecRegister +================= +*/ +static void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit (void) +{ + snd_codec_t *codec; + codecs = NULL; + + /* Register in the inverse order + * of codec choice preference: */ +#ifdef USE_CODEC_UMX + S_CodecRegister(&umx_codec); +#endif +#ifdef USE_CODEC_MIKMOD + S_CodecRegister(&mikmod_codec); +#endif +#ifdef USE_CODEC_XMP + S_CodecRegister(&xmp_codec); +#endif +#ifdef USE_CODEC_WAVE + S_CodecRegister(&wav_codec); +#endif +#ifdef USE_CODEC_FLAC + S_CodecRegister(&flac_codec); +#endif +#ifdef USE_CODEC_MP3 + S_CodecRegister(&mp3_codec); +#endif +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&vorbis_codec); +#endif +#ifdef USE_CODEC_OPUS + S_CodecRegister(&opus_codec); +#endif + + codec = codecs; + while (codec) + { + codec->initialize(); + codec = codec->next; + } +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown (void) +{ + snd_codec_t *codec = codecs; + while (codec) + { + codec->shutdown(); + codec = codec->next; + } + codecs = NULL; +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type) +{ + snd_codec_t *codec; + snd_stream_t *stream; + + if (type == CODECTYPE_NONE) + { + Con_Printf("Bad type for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (type == codec->type) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown type for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; +} + +snd_stream_t *S_CodecOpenStreamExt (const char *filename) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = COM_FileGetExtension(filename); + if (! *ext) + { + Con_Printf("No extension for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (!q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; +} + +snd_stream_t *S_CodecOpenStreamAny (const char *filename) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = COM_FileGetExtension(filename); + if (! *ext) /* try all available */ + { + char tmp[MAX_QPATH]; + + codec = codecs; + while (codec) + { + q_snprintf(tmp, sizeof(tmp), "%s.%s", filename, codec->ext); + stream = S_CodecUtilOpen(tmp, codec); + if (stream) { + if (codec->codec_open(stream)) { + stream->status = STREAM_PLAY; + return stream; + } + S_CodecUtilClose(&stream); + } + codec = codec->next; + } + + return NULL; + } + else /* use the name as is */ + { + codec = codecs; + while (codec) + { + if (!q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; + } +} + +qboolean S_CodecForwardStream (snd_stream_t *stream, unsigned int type) +{ + snd_codec_t *codec = codecs; + + while (codec) + { + if (type == codec->type) + break; + codec = codec->next; + } + if (!codec) return false; + stream->codec = codec; + return codec->codec_open(stream); +} + +void S_CodecCloseStream (snd_stream_t *stream) +{ + stream->status = STREAM_NONE; + stream->codec->codec_close(stream); +} + +int S_CodecRewindStream (snd_stream_t *stream) +{ + return stream->codec->codec_rewind(stream); +} + +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->codec_read(stream, bytes, buffer); +} + +/* Util functions (used by codecs) */ + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + FILE *handle; + qboolean pak; + long length; + + /* Try to open the file */ + length = (long) COM_FOpenFile(filename, &handle, NULL); + pak = file_from_pak; + if (length == -1) + { + Con_DPrintf("Couldn't open %s\n", filename); + return NULL; + } + + /* Allocate a stream, Z_Malloc zeroes its content */ + stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t)); + stream->codec = codec; + stream->fh.file = handle; + stream->fh.start = ftell(handle); + stream->fh.pos = 0; + stream->fh.length = length; + stream->fh.pak = stream->pak = pak; + q_strlcpy(stream->name, filename, MAX_QPATH); + + return stream; +} + +void S_CodecUtilClose(snd_stream_t **stream) +{ + fclose((*stream)->fh.file); + Z_Free(*stream); + *stream = NULL; +} + +int S_CodecIsAvailable (unsigned int type) +{ + snd_codec_t *codec = codecs; + while (codec) + { + if (type == codec->type) + return codec->initialized; + codec = codec->next; + } + return -1; +} + diff --git a/source/snd_codec.h b/source/snd_codec.h new file mode 100644 index 0000000..657b0b2 --- /dev/null +++ b/source/snd_codec.h @@ -0,0 +1,104 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +typedef struct snd_info_s +{ + int rate; + int bits, width; + int channels; + int samples; + int blocksize; + int size; + int dataofs; +} snd_info_t; + +typedef enum { + STREAM_NONE = -1, + STREAM_INIT, + STREAM_PAUSE, + STREAM_PLAY +} stream_status_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + fshandle_t fh; + qboolean pak; + char name[MAX_QPATH]; /* name of the source file */ + snd_info_t info; + stream_status_t status; + snd_codec_t *codec; /* codec handling this stream */ + void *priv; /* data private to the codec. */ +} snd_stream_t; + + +void S_CodecInit (void); +void S_CodecShutdown (void); + +/* Callers of the following S_CodecOpenStream* functions + * are reponsible for attaching any path to the filename */ + +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type); + /* Decides according to the required type. */ + +snd_stream_t *S_CodecOpenStreamAny (const char *filename); + /* Decides according to file extension. if the + * name has no extension, try all available. */ + +snd_stream_t *S_CodecOpenStreamExt (const char *filename); + /* Decides according to file extension. the name + * MUST have an extension. */ + +void S_CodecCloseStream (snd_stream_t *stream); +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer); +int S_CodecRewindStream (snd_stream_t *stream); + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + + +#define CODECTYPE_NONE 0 +#define CODECTYPE_MID (1U << 0) +#define CODECTYPE_MOD (1U << 1) +#define CODECTYPE_FLAC (1U << 2) +#define CODECTYPE_WAV (1U << 3) +#define CODECTYPE_MP3 (1U << 4) +#define CODECTYPE_VORBIS (1U << 5) +#define CODECTYPE_OPUS (1U << 6) +#define CODECTYPE_UMX (1U << 7) + +#define CODECTYPE_WAVE CODECTYPE_WAV +#define CODECTYPE_MIDI CODECTYPE_MID + +int S_CodecIsAvailable (unsigned int type); + /* return 1 if available, 0 if codec failed init + * or -1 if no such codec is present. */ + +#endif /* _SND_CODEC_H_ */ + diff --git a/source/snd_codeci.h b/source/snd_codeci.h new file mode 100644 index 0000000..feedb61 --- /dev/null +++ b/source/snd_codeci.h @@ -0,0 +1,55 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODECI_H_ +#define _SND_CODECI_H_ + +/* Codec internals */ +typedef qboolean (*CODEC_INIT)(void); +typedef void (*CODEC_SHUTDOWN)(void); +typedef qboolean (*CODEC_OPEN)(snd_stream_t *stream); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef int (*CODEC_REWIND)(snd_stream_t *stream); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +struct snd_codec_s +{ + unsigned int type; /* handled data type. (1U << n) */ + qboolean initialized; /* init succeedded */ + const char *ext; /* expected extension */ + CODEC_INIT initialize; + CODEC_SHUTDOWN shutdown; + CODEC_OPEN codec_open; + CODEC_READ codec_read; + CODEC_REWIND codec_rewind; + CODEC_CLOSE codec_close; + snd_codec_t *next; +}; + +qboolean S_CodecForwardStream (snd_stream_t *stream, unsigned int type); + /* Forward a stream to another codec of 'type' type. */ + +#endif /* _SND_CODECI_H_ */ + diff --git a/source/snd_dma.c b/source/snd_dma.c new file mode 100644 index 0000000..cc5df69 --- /dev/null +++ b/source/snd_dma.c @@ -0,0 +1,1070 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2011 O. Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +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_dma.c -- main control for any streaming sound output device + +#include "quakedef.h" +#include "snd_codec.h" +#include "bgmusic.h" + +static void S_Play (void); +static void S_PlayVol (void); +static void S_SoundList (void); +static void S_Update_ (void); +void S_StopAllSounds (qboolean clear); +static void S_StopAllSoundsC (void); + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +channel_t snd_channels[MAX_CHANNELS]; +int total_channels; + +static int snd_blocked = 0; +static qboolean snd_initialized = false; + +static dma_t sn; +volatile dma_t *shm = NULL; + +vec3_t listener_origin; +vec3_t listener_forward; +vec3_t listener_right; +vec3_t listener_up; + +#define sound_nominal_clip_dist 1000.0 + +int soundtime; // sample PAIRS +int paintedtime; // sample PAIRS + +int s_rawend; +portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + + +#define MAX_SFX 1024 +static sfx_t *known_sfx = NULL; // hunk allocated [MAX_SFX] +static int num_sfx; + +static sfx_t *ambient_sfx[NUM_AMBIENTS]; + +static qboolean sound_started = false; + +cvar_t bgmvolume = {"bgmvolume", "1", CVAR_ARCHIVE}; +cvar_t sfxvolume = {"volume", "0.7", CVAR_ARCHIVE}; + +cvar_t precache = {"precache", "1", CVAR_NONE}; +cvar_t loadas8bit = {"loadas8bit", "0", CVAR_NONE}; + +cvar_t sndspeed = {"sndspeed", "11025", CVAR_NONE}; +cvar_t snd_mixspeed = {"snd_mixspeed", "44100", CVAR_NONE}; + +#if defined(_WIN32) +#define SND_FILTERQUALITY_DEFAULT "5" +#else +#define SND_FILTERQUALITY_DEFAULT "1" +#endif + +cvar_t snd_filterquality = {"snd_filterquality", SND_FILTERQUALITY_DEFAULT, + CVAR_NONE}; + +static cvar_t nosound = {"nosound", "0", CVAR_NONE}; +static cvar_t ambient_level = {"ambient_level", "0.3", CVAR_NONE}; +static cvar_t ambient_fade = {"ambient_fade", "100", CVAR_NONE}; +static cvar_t snd_noextraupdate = {"snd_noextraupdate", "0", CVAR_NONE}; +static cvar_t snd_show = {"snd_show", "0", CVAR_NONE}; +static cvar_t _snd_mixahead = {"_snd_mixahead", "0.1", CVAR_ARCHIVE}; + + +static void S_SoundInfo_f (void) +{ + if (!sound_started || !shm) + { + Con_Printf ("sound system not started\n"); + return; + } + + Con_Printf("%d bit, %s, %d Hz\n", shm->samplebits, + (shm->channels == 2) ? "stereo" : "mono", shm->speed); + Con_Printf("%5d samples\n", shm->samples); + Con_Printf("%5d samplepos\n", shm->samplepos); + Con_Printf("%5d submission_chunk\n", shm->submission_chunk); + Con_Printf("%5d total_channels\n", total_channels); + Con_Printf("%p dma buffer\n", shm->buffer); +} + + +static void SND_Callback_sfxvolume (cvar_t *var) +{ + SND_InitScaletable (); +} + +static void SND_Callback_snd_filterquality (cvar_t *var) +{ + if (snd_filterquality.value < 1 || snd_filterquality.value > 5) + { + Con_Printf ("snd_filterquality must be between 1 and 5\n"); + Cvar_SetQuick (&snd_filterquality, SND_FILTERQUALITY_DEFAULT); + } +} + +/* +================ +S_Startup +================ +*/ +void S_Startup (void) +{ + if (!snd_initialized) + return; + + sound_started = SNDDMA_Init(&sn); + + if (!sound_started) + { + Con_Printf("Failed initializing sound\n"); + } + else + { + Con_Printf("Audio: %d bit, %s, %d Hz\n", + shm->samplebits, + (shm->channels == 2) ? "stereo" : "mono", + shm->speed); + } +} + + +/* +================ +S_Init +================ +*/ +void S_Init (void) +{ + int i; + + if (snd_initialized) + { + Con_Printf("Sound is already initialized\n"); + return; + } + + Cvar_RegisterVariable(&nosound); + Cvar_RegisterVariable(&sfxvolume); + Cvar_RegisterVariable(&precache); + Cvar_RegisterVariable(&loadas8bit); + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&ambient_level); + Cvar_RegisterVariable(&ambient_fade); + Cvar_RegisterVariable(&snd_noextraupdate); + Cvar_RegisterVariable(&snd_show); + Cvar_RegisterVariable(&_snd_mixahead); + Cvar_RegisterVariable(&sndspeed); + Cvar_RegisterVariable(&snd_mixspeed); + Cvar_RegisterVariable(&snd_filterquality); + + if (safemode || COM_CheckParm("-nosound")) + return; + + Con_Printf("\nSound Initialization\n"); + + Cmd_AddCommand("play", S_Play); + Cmd_AddCommand("playvol", S_PlayVol); + Cmd_AddCommand("stopsound", S_StopAllSoundsC); + Cmd_AddCommand("soundlist", S_SoundList); + Cmd_AddCommand("soundinfo", S_SoundInfo_f); + + i = COM_CheckParm("-sndspeed"); + if (i && i < com_argc-1) + { + Cvar_SetQuick (&sndspeed, com_argv[i+1]); + } + + i = COM_CheckParm("-mixspeed"); + if (i && i < com_argc-1) + { + Cvar_SetQuick (&snd_mixspeed, com_argv[i+1]); + } + + if (host_parms->memsize < 0x800000) + { + Cvar_SetQuick (&loadas8bit, "1"); + Con_Printf ("loading all sounds as 8bit\n"); + } + + Cvar_SetCallback(&sfxvolume, SND_Callback_sfxvolume); + Cvar_SetCallback(&snd_filterquality, &SND_Callback_snd_filterquality); + + SND_InitScaletable (); + + known_sfx = (sfx_t *) Hunk_AllocName (MAX_SFX*sizeof(sfx_t), "sfx_t"); + num_sfx = 0; + + snd_initialized = true; + + S_Startup (); + if (sound_started == 0) + return; + +// provides a tick sound until washed clean +// if (shm->buffer) +// shm->buffer[4] = shm->buffer[5] = 0x7f; // force a pop for debugging + + ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav"); + ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav"); + + S_CodecInit (); + + S_StopAllSounds (true); +} + + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= +void S_Shutdown (void) +{ + if (!sound_started) + return; + + sound_started = 0; + snd_blocked = 0; + + S_CodecShutdown(); + + SNDDMA_Shutdown(); + shm = NULL; +} + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================== +S_FindName + +================== +*/ +static sfx_t *S_FindName (const char *name) +{ + int i; + sfx_t *sfx; + + if (!name) + Sys_Error ("S_FindName: NULL"); + + if (Q_strlen(name) >= MAX_QPATH) + Sys_Error ("Sound name too long: %s", name); + +// see if already loaded + for (i = 0; i < num_sfx; i++) + { + if (!Q_strcmp(known_sfx[i].name, name)) + { + return &known_sfx[i]; + } + } + + if (num_sfx == MAX_SFX) + Sys_Error ("S_FindName: out of sfx_t"); + + sfx = &known_sfx[i]; + q_strlcpy (sfx->name, name, sizeof(sfx->name)); + + num_sfx++; + + return sfx; +} + + +/* +================== +S_TouchSound + +================== +*/ +void S_TouchSound (const char *name) +{ + sfx_t *sfx; + + if (!sound_started) + return; + + sfx = S_FindName (name); + Cache_Check (&sfx->cache); +} + +/* +================== +S_PrecacheSound + +================== +*/ +sfx_t *S_PrecacheSound (const char *name) +{ + sfx_t *sfx; + + if (!sound_started || nosound.value) + return NULL; + + sfx = S_FindName (name); + +// cache it in + if (precache.value) + S_LoadSound (sfx); + + return sfx; +} + + +//============================================================================= + +/* +================= +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 life_left; + +// Check for replacement sound, or find the best one to replace + first_to_die = -1; + life_left = 0x7fffffff; + for (ch_idx = NUM_AMBIENTS; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS; ch_idx++) + { + if (entchannel != 0 // channel 0 never overrides + && snd_channels[ch_idx].entnum == entnum + && (snd_channels[ch_idx].entchannel == entchannel || entchannel == -1) ) + { // always override sound from same entity + first_to_die = ch_idx; + break; + } + + // don't let monster sounds override player sounds + if (snd_channels[ch_idx].entnum == cl.viewentity && entnum != cl.viewentity && snd_channels[ch_idx].sfx) + continue; + + if (snd_channels[ch_idx].end - paintedtime < life_left) + { + life_left = snd_channels[ch_idx].end - paintedtime; + first_to_die = ch_idx; + } + } + + if (first_to_die == -1) + return NULL; + + if (snd_channels[first_to_die].sfx) + snd_channels[first_to_die].sfx = NULL; + + return &snd_channels[first_to_die]; +} + +/* +================= +SND_Spatialize + +spatializes a channel +================= +*/ +void SND_Spatialize (channel_t *ch) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + +// anything coming from the view entity will always be full volume + if (ch->entnum == cl.viewentity) + { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + return; + } + +// calculate stereo seperation and distance attenuation + VectorSubtract(ch->origin, listener_origin, source_vec); + dist = VectorNormalize(source_vec) * ch->dist_mult; + dot = DotProduct(listener_right, source_vec); + + if (shm->channels == 1) + { + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 1.0 + dot; + lscale = 1.0 - dot; + } + +// add in distance effect + scale = (1.0 - dist) * rscale; + ch->rightvol = (int) (ch->master_vol * scale); + if (ch->rightvol < 0) + ch->rightvol = 0; + + scale = (1.0 - dist) * lscale; + ch->leftvol = (int) (ch->master_vol * scale); + if (ch->leftvol < 0) + ch->leftvol = 0; +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +void S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + channel_t *target_chan, *check; + sfxcache_t *sc; + int ch_idx; + int skip; + + if (!sound_started) + return; + + if (!sfx) + return; + + if (nosound.value) + return; + +// pick a channel to play on + target_chan = SND_PickChannel(entnum, entchannel); + if (!target_chan) + return; + +// spatialize + memset (target_chan, 0, sizeof(*target_chan)); + VectorCopy(origin, target_chan->origin); + target_chan->dist_mult = attenuation / sound_nominal_clip_dist; + target_chan->master_vol = (int) (fvol * 255); + target_chan->entnum = entnum; + target_chan->entchannel = entchannel; + SND_Spatialize(target_chan); + + if (!target_chan->leftvol && !target_chan->rightvol) + return; // not audible at all + +// new channel + sc = S_LoadSound (sfx); + if (!sc) + { + target_chan->sfx = NULL; + return; // couldn't load the sound's data + } + + target_chan->sfx = sfx; + target_chan->pos = 0.0; + target_chan->end = paintedtime + sc->length; + +// 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 = &snd_channels[NUM_AMBIENTS]; + 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) + { + /* + skip = rand () % (int)(0.1 * shm->speed); + if (skip >= target_chan->end) + skip = target_chan->end - 1; + */ + /* LordHavoc: fixed skip calculations */ + skip = 0.1 * shm->speed; /* 0.1 * sc->speed */ + if (skip > sc->length) + skip = sc->length; + if (skip > 0) + skip = rand() % skip; + target_chan->pos += skip; + target_chan->end -= skip; + break; + } + } +} + +void S_StopSound (int entnum, int entchannel) +{ + int i; + + for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) + { + if (snd_channels[i].entnum == entnum + && snd_channels[i].entchannel == entchannel) + { + snd_channels[i].end = 0; + snd_channels[i].sfx = NULL; + return; + } + } +} + +void S_StopAllSounds (qboolean clear) +{ + int i; + + if (!sound_started) + return; + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + + for (i = 0; i < MAX_CHANNELS; i++) + { + if (snd_channels[i].sfx) + snd_channels[i].sfx = NULL; + } + + memset(snd_channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + if (clear) + S_ClearBuffer (); +} + +static void S_StopAllSoundsC (void) +{ + S_StopAllSounds (true); +} + +void S_ClearBuffer (void) +{ + int clear; + + if (!sound_started || !shm) + return; + + SNDDMA_LockBuffer (); + if (! shm->buffer) + return; + + s_rawend = 0; + + if (shm->samplebits == 8 && !shm->signed8) + clear = 0x80; + else + clear = 0; + + memset(shm->buffer, clear, shm->samples * shm->samplebits / 8); + + SNDDMA_Submit (); +} + + +/* +================= +S_StaticSound +================= +*/ +void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation) +{ + channel_t *ss; + sfxcache_t *sc; + + if (!sfx) + return; + + if (total_channels == MAX_CHANNELS) + { + Con_Printf ("total_channels == MAX_CHANNELS\n"); + return; + } + + ss = &snd_channels[total_channels]; + total_channels++; + + sc = S_LoadSound (sfx); + if (!sc) + return; + + if (sc->loopstart == -1) + { + Con_Printf ("Sound %s not looped\n", sfx->name); + return; + } + + ss->sfx = sfx; + VectorCopy (origin, ss->origin); + ss->master_vol = (int)vol; + ss->dist_mult = (attenuation / 64) / sound_nominal_clip_dist; + ss->end = paintedtime + sc->length; + + SND_Spatialize (ss); +} + + +//============================================================================= + +/* +=================== +S_UpdateAmbientSounds +=================== +*/ +static void S_UpdateAmbientSounds (void) +{ + mleaf_t *l; + int vol, ambient_channel; + channel_t *chan; + +// no ambients when disconnected + if (cls.state != ca_connected) + return; +// calc ambient sound levels + if (!cl.worldmodel) + return; + + l = Mod_PointInLeaf (listener_origin, cl.worldmodel); + if (!l || !ambient_level.value) + { + for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++) + snd_channels[ambient_channel].sfx = NULL; + return; + } + + for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++) + { + chan = &snd_channels[ambient_channel]; + chan->sfx = ambient_sfx[ambient_channel]; + + vol = (int) (ambient_level.value * l->ambient_sound_level[ambient_channel]); + if (vol < 8) + vol = 0; + + // don't adjust volume too fast + if (chan->master_vol < vol) + { + chan->master_vol += (int) (host_frametime * ambient_fade.value); + if (chan->master_vol > vol) + chan->master_vol = vol; + } + else if (chan->master_vol > vol) + { + chan->master_vol -= (int) (host_frametime * ambient_fade.value); + if (chan->master_vol < vol) + chan->master_vol = vol; + } + + chan->leftvol = chan->rightvol = chan->master_vol; + } +} + + +/* +=================== +S_RawSamples (from QuakeII) + +Streaming music support. Byte swapping +of data must be handled by the codec. +Expects data in signed 16 bit, or unsigned +8 bit format. +=================== +*/ +void S_RawSamples (int samples, int rate, int width, int channels, byte *data, float volume) +{ + int i; + int src, dst; + float scale; + int intVolume; + + if (s_rawend < paintedtime) + s_rawend = paintedtime; + + scale = (float) rate / shm->speed; + intVolume = (int) (256 * volume); + + if (channels == 2 && width == 2) + { + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + s_rawsamples [dst].left = ((short *) data)[src * 2] * intVolume; + s_rawsamples [dst].right = ((short *) data)[src * 2 + 1] * intVolume; + } + } + else if (channels == 1 && width == 2) + { + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + s_rawsamples [dst].left = ((short *) data)[src] * intVolume; + s_rawsamples [dst].right = ((short *) data)[src] * intVolume; + } + } + else if (channels == 2 && width == 1) + { + intVolume *= 256; + + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + // s_rawsamples [dst].left = ((signed char *) data)[src * 2] * intVolume; + // s_rawsamples [dst].right = ((signed char *) data)[src * 2 + 1] * intVolume; + s_rawsamples [dst].left = (((byte *) data)[src * 2] - 128) * intVolume; + s_rawsamples [dst].right = (((byte *) data)[src * 2 + 1] - 128) * intVolume; + } + } + else if (channels == 1 && width == 1) + { + intVolume *= 256; + + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + // s_rawsamples [dst].left = ((signed char *) data)[src] * intVolume; + // s_rawsamples [dst].right = ((signed char *) data)[src] * intVolume; + s_rawsamples [dst].left = (((byte *) data)[src] - 128) * intVolume; + s_rawsamples [dst].right = (((byte *) data)[src] - 128) * intVolume; + } + } +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) +{ + int i, j; + int total; + channel_t *ch; + channel_t *combine; + + if (!sound_started || (snd_blocked > 0)) + return; + + VectorCopy(origin, listener_origin); + VectorCopy(forward, listener_forward); + VectorCopy(right, listener_right); + VectorCopy(up, listener_up); + +// update general area ambient sound sources + S_UpdateAmbientSounds (); + + combine = NULL; + +// update spatialization for static and dynamic sounds + ch = snd_channels + NUM_AMBIENTS; + for (i = NUM_AMBIENTS; i < total_channels; i++, ch++) + { + if (!ch->sfx) + continue; + SND_Spatialize(ch); // respatialize channel + if (!ch->leftvol && !ch->rightvol) + continue; + + // 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) + { + // see if it can just use the last one + if (combine && combine->sfx == ch->sfx) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + continue; + } + // search for one + combine = snd_channels + MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; + for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; j < i; j++, combine++) + { + if (combine->sfx == ch->sfx) + break; + } + + if (j == total_channels) + { + combine = NULL; + } + else + { + if (combine != ch) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + } + continue; + } + } + } + +// +// debugging output +// + if (snd_show.value) + { + total = 0; + ch = snd_channels; + for (i = 0; i < total_channels; i++, ch++) + { + if (ch->sfx && (ch->leftvol || ch->rightvol) ) + { + // Con_Printf ("%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name); + total++; + } + } + + Con_Printf ("----(%i)----\n", total); + } + +// add raw data from streamed samples +// BGM_Update(); // moved to the main loop just before S_Update () + +// mix some sound + S_Update_(); +} + +static void GetSoundtime (void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = shm->samples / shm->channels; + +// it is possible to miscount buffers if it has wrapped twice between +// calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + paintedtime = fullsamples; + S_StopAllSounds (true); + } + } + oldsamplepos = samplepos; + + soundtime = buffers*fullsamples + samplepos/shm->channels; +} + +void S_ExtraUpdate (void) +{ + if (snd_noextraupdate.value) + return; // don't pollute timings + S_Update_(); +} + +static void S_Update_ (void) +{ + unsigned int endtime; + int samps; + + if (!sound_started || (snd_blocked > 0)) + return; + + SNDDMA_LockBuffer (); + if (! shm->buffer) + return; + +// Updates DMA time + GetSoundtime(); + +// check to make sure that we haven't overshot + if (paintedtime < soundtime) + { + // Con_Printf ("S_Update_ : overflow\n"); + paintedtime = soundtime; + } + +// mix ahead of current position + endtime = soundtime + (unsigned int)(_snd_mixahead.value * shm->speed); + samps = shm->samples >> (shm->channels - 1); + endtime = q_min(endtime, (unsigned int)(soundtime + samps)); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); +} + +void S_BlockSound (void) +{ +/* FIXME: do we really need the blocking at the + * driver level? + */ + if (sound_started && snd_blocked == 0) /* ++snd_blocked == 1 */ + { + snd_blocked = 1; + S_ClearBuffer (); + if (shm) + SNDDMA_BlockSound(); + } +} + +void S_UnblockSound (void) +{ + if (!sound_started || !snd_blocked) + return; + if (snd_blocked == 1) /* --snd_blocked == 0 */ + { + snd_blocked = 0; + SNDDMA_UnblockSound(); + S_ClearBuffer (); + } +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +static void S_Play (void) +{ + static int hash = 345; + int i; + char name[256]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc()) + { + q_strlcpy(name, Cmd_Argv(i), sizeof(name)); + if (!Q_strrchr(Cmd_Argv(i), '.')) + { + q_strlcat(name, ".wav", sizeof(name)); + } + sfx = S_PrecacheSound(name); + S_StartSound(hash++, 0, sfx, listener_origin, 1.0, 1.0); + i++; + } +} + +static void S_PlayVol (void) +{ + static int hash = 543; + int i; + float vol; + char name[256]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc()) + { + q_strlcpy(name, Cmd_Argv(i), sizeof(name)); + if (!Q_strrchr(Cmd_Argv(i), '.')) + { + q_strlcat(name, ".wav", sizeof(name)); + } + sfx = S_PrecacheSound(name); + vol = Q_atof(Cmd_Argv(i + 1)); + S_StartSound(hash++, 0, sfx, listener_origin, vol, 1.0); + i += 2; + } +} + +static void S_SoundList (void) +{ + int i; + sfx_t *sfx; + sfxcache_t *sc; + int size, total; + + total = 0; + for (sfx = known_sfx, i = 0; i < num_sfx; i++, sfx++) + { + sc = (sfxcache_t *) Cache_Check (&sfx->cache); + if (!sc) + continue; + size = sc->length*sc->width*(sc->stereo + 1); + total += size; + if (sc->loopstart >= 0) + Con_SafePrintf ("L"); //johnfitz -- was Con_Printf + else + Con_SafePrintf (" "); //johnfitz -- was Con_Printf + Con_SafePrintf("(%2db) %6i : %s\n", sc->width*8, size, sfx->name); //johnfitz -- was Con_Printf + } + Con_Printf ("%i sounds, %i bytes\n", num_sfx, total); //johnfitz -- added count +} + + +void S_LocalSound (const char *name) +{ + sfx_t *sfx; + + if (nosound.value) + return; + if (!sound_started) + return; + + sfx = S_PrecacheSound (name); + if (!sfx) + { + Con_Printf ("S_LocalSound: can't cache %s\n", name); + return; + } + S_StartSound (cl.viewentity, -1, sfx, vec3_origin, 1, 1); +} + + +void S_ClearPrecache (void) +{ +} + + +void S_BeginPrecaching (void) +{ +} + + +void S_EndPrecaching (void) +{ +} + diff --git a/source/snd_flac.c b/source/snd_flac.c new file mode 100644 index 0000000..b5417b3 --- /dev/null +++ b/source/snd_flac.c @@ -0,0 +1,390 @@ +/* + * fLaC streaming music support, loosely based QuakeForge implementation + * with modifications. requires libFLAC >= 1.0.4 at compile and runtime. + * + * Copyright (C) 2005 Bill Currie + * Copyright (C) 2013 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_FLAC) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_flac.h" + +#undef LEGACY_FLAC +#include +/* FLAC 1.1.3 has FLAC_API_VERSION_CURRENT == 8 */ +#if !defined(FLAC_API_VERSION_CURRENT) || ((FLAC_API_VERSION_CURRENT+0) < 8) +#define LEGACY_FLAC +#include +#endif + +#ifdef LEGACY_FLAC +#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder +#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus +#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus +#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus +#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus + +#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new +#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish +#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete +#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single +#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute +#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata +#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state + +#define FLAC__STREAM_DECODER_INIT_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_OK +#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR +#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK /* !!! */ +#define FLAC__STREAM_DECODER_WRITE_STATUS_ABORT FLAC__STREAM_DECODER_WRITE_STATUS_ABORT +#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR +#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +typedef unsigned FLAC_SIZE_T; +#else +typedef size_t FLAC_SIZE_T; +#endif + +typedef struct { + FLAC__StreamDecoder *decoder; + fshandle_t *file; + snd_info_t *info; + byte *buffer; + int size, pos, error; +} flacfile_t; + +/* CALLBACK FUNCTIONS: */ +static void +flac_error_func (const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + ff->error = -1; + Con_Printf ("FLAC: decoder error %i\n", status); +} + +static FLAC__StreamDecoderReadStatus +flac_read_func (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], + FLAC_SIZE_T *bytes, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (*bytes > 0) + { + *bytes = FS_fread(buffer, 1, *bytes, ff->file); + if (FS_ferror(ff->file)) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + if (*bytes == 0) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; +} + +static FLAC__StreamDecoderSeekStatus +flac_seek_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (FS_fseek(ff->file, (long)absolute_byte_offset, SEEK_SET) < 0) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +static FLAC__StreamDecoderTellStatus +flac_tell_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + long pos = FS_ftell (ff->file); + if (pos < 0) return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + *absolute_byte_offset = (FLAC__uint64) pos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +static FLAC__StreamDecoderLengthStatus +flac_length_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + *stream_length = (FLAC__uint64) FS_filelength (ff->file); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool +flac_eof_func (const FLAC__StreamDecoder *decoder, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (FS_feof (ff->file)) return true; + return false; +} + +static FLAC__StreamDecoderWriteStatus +flac_write_func (const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, const FLAC__int32 * const buffer[], + void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + + if (!ff->buffer) { + ff->buffer = (byte *) malloc (ff->info->blocksize * ff->info->channels * ff->info->width); + if (!ff->buffer) { + ff->error = -1; /* needn't set this here, but... */ + Con_Printf("Insufficient memory for fLaC audio\n"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + } + + if (ff->info->channels == 1) + { + unsigned i; + const FLAC__int32 *in = buffer[0]; + + if (ff->info->bits == 8) + { + byte *out = ff->buffer; + for (i = 0; i < frame->header.blocksize; i++) + *out++ = *in++ + 128; + } + else + { + short *out = (short *) ff->buffer; + for (i = 0; i < frame->header.blocksize; i++) + *out++ = *in++; + } + } + else + { + unsigned i; + const FLAC__int32 *li = buffer[0]; + const FLAC__int32 *ri = buffer[1]; + + if (ff->info->bits == 8) + { + char *lo = (char *) ff->buffer + 0; + char *ro = (char *) ff->buffer + 1; + for (i = 0; i < frame->header.blocksize; i++, lo++, ro++) + { + *lo++ = *li++ + 128; + *ro++ = *ri++ + 128; + } + } + else + { + short *lo = (short *) ff->buffer + 0; + short *ro = (short *) ff->buffer + 1; + for (i = 0; i < frame->header.blocksize; i++, lo++, ro++) + { + *lo++ = *li++; + *ro++ = *ri++; + } + } + } + + ff->size = frame->header.blocksize * ff->info->width * ff->info->channels; + ff->pos = 0; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void +flac_meta_func (const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + { + ff->info->rate = metadata->data.stream_info.sample_rate; + ff->info->bits = metadata->data.stream_info.bits_per_sample; + ff->info->width = ff->info->bits / 8; + ff->info->channels = metadata->data.stream_info.channels; + ff->info->blocksize = metadata->data.stream_info.max_blocksize; + ff->info->dataofs = 0; /* got the STREAMINFO metadata */ + } +} + + +static qboolean S_FLAC_CodecInitialize (void) +{ + return true; +} + +static void S_FLAC_CodecShutdown (void) +{ +} + +static qboolean S_FLAC_CodecOpenStream (snd_stream_t *stream) +{ + flacfile_t *ff; + int rc; + + ff = (flacfile_t *) Z_Malloc(sizeof(flacfile_t)); + + ff->decoder = FLAC__stream_decoder_new (); + if (ff->decoder == NULL) + { + Con_Printf("Unable to create fLaC decoder\n"); + goto _fail; + } + + stream->priv = ff; + ff->info = & stream->info; + ff->file = & stream->fh; + ff->info->dataofs = -1; /* check for STREAMINFO metadata existence */ + +#ifdef LEGACY_FLAC + FLAC__seekable_stream_decoder_set_error_callback (ff->decoder, flac_error_func); + FLAC__seekable_stream_decoder_set_read_callback (ff->decoder, flac_read_func); + FLAC__seekable_stream_decoder_set_seek_callback (ff->decoder, flac_seek_func); + FLAC__seekable_stream_decoder_set_tell_callback (ff->decoder, flac_tell_func); + FLAC__seekable_stream_decoder_set_length_callback (ff->decoder, flac_length_func); + FLAC__seekable_stream_decoder_set_eof_callback (ff->decoder, flac_eof_func); + FLAC__seekable_stream_decoder_set_write_callback (ff->decoder, flac_write_func); + FLAC__seekable_stream_decoder_set_metadata_callback (ff->decoder, flac_meta_func); + FLAC__seekable_stream_decoder_set_client_data (ff->decoder, ff); + rc = FLAC__seekable_stream_decoder_init (ff->decoder); +#else + rc = FLAC__stream_decoder_init_stream(ff->decoder, + flac_read_func, + flac_seek_func, + flac_tell_func, + flac_length_func, + flac_eof_func, + flac_write_func, + flac_meta_func, + flac_error_func, + ff); +#endif + if (rc != FLAC__STREAM_DECODER_INIT_STATUS_OK) /* unlikely */ + { + Con_Printf ("FLAC: decoder init error %i\n", rc); + goto _fail; + } + + rc = FLAC__stream_decoder_process_until_end_of_metadata (ff->decoder); + if (rc == false || ff->error) + { + rc = FLAC__stream_decoder_get_state(ff->decoder); + Con_Printf("%s not a valid flac file? (decoder state %i)\n", + stream->name, rc); + goto _fail; + } + + if (ff->info->dataofs < 0) + { + Con_Printf("%s has no STREAMINFO\n", stream->name); + goto _fail; + } + if (ff->info->bits != 8 && ff->info->bits != 16) + { + Con_Printf("%s is not 8 or 16 bit\n", stream->name); + goto _fail; + } + if (ff->info->channels != 1 && ff->info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + ff->info->channels, stream->name); + goto _fail; + } + + return true; + +_fail: + if (ff->decoder) + { + FLAC__stream_decoder_finish (ff->decoder); + FLAC__stream_decoder_delete (ff->decoder); + } + Z_Free(ff); + return false; +} + +static int S_FLAC_CodecReadStream (snd_stream_t *stream, int len, void *buffer) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + byte *buf = (byte *) buffer; + int count = 0; + + while (len) { + int res = 0; + if (ff->size == ff->pos) + FLAC__stream_decoder_process_single (ff->decoder); + if (ff->error) return -1; + res = ff->size - ff->pos; + if (res > len) + res = len; + if (res > 0) { + memcpy (buf, ff->buffer + ff->pos, res); + count += res; + len -= res; + buf += res; + ff->pos += res; + } else if (res < 0) { /* error */ + return -1; + } else { + Con_DPrintf ("FLAC: EOF\n"); + break; + } + } + return count; +} + +static void S_FLAC_CodecCloseStream (snd_stream_t *stream) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + + FLAC__stream_decoder_finish (ff->decoder); + FLAC__stream_decoder_delete (ff->decoder); + + if (ff->buffer) + free(ff->buffer); + Z_Free(ff); + + S_CodecUtilClose(&stream); +} + +static int S_FLAC_CodecRewindStream (snd_stream_t *stream) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + + ff->pos = ff->size = 0; + if (FLAC__stream_decoder_seek_absolute(ff->decoder, 0)) return 0; + return -1; +} + +snd_codec_t flac_codec = +{ + CODECTYPE_FLAC, + true, /* always available. */ + "flac", + S_FLAC_CodecInitialize, + S_FLAC_CodecShutdown, + S_FLAC_CodecOpenStream, + S_FLAC_CodecReadStream, + S_FLAC_CodecRewindStream, + S_FLAC_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_FLAC */ + diff --git a/source/snd_flac.h b/source/snd_flac.h new file mode 100644 index 0000000..4cedf82 --- /dev/null +++ b/source/snd_flac.h @@ -0,0 +1,13 @@ +/* fLaC streaming music support. */ + +#if !defined(_SND_FLAC_H_) +#define _SND_FLAC_H_ 1 + +#if defined(USE_CODEC_FLAC) + +extern snd_codec_t flac_codec; + +#endif /* USE_CODEC_FLAC */ + +#endif /* ! _SND_FLAC_H_ */ + diff --git a/source/snd_mem.c b/source/snd_mem.c new file mode 100644 index 0000000..239e020 --- /dev/null +++ b/source/snd_mem.c @@ -0,0 +1,352 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2011 O. Sezer + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_mem.c: sound caching + +#include "quakedef.h" + +/* +================ +ResampleSfx +================ +*/ +static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) +{ + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + sfxcache_t *sc; + + sc = (sfxcache_t *) Cache_Check (&sfx->cache); + if (!sc) + return; + + stepscale = (float)inrate / shm->speed; // this is usually 0.5, 1, or 2 + + outcount = sc->length / stepscale; + sc->length = outcount; + if (sc->loopstart != -1) + sc->loopstart = sc->loopstart / stepscale; + + sc->speed = shm->speed; + if (loadas8bit.value) + sc->width = 1; + else + sc->width = inwidth; + sc->stereo = 0; + +// resample / decimate to the current source rate + + if (stepscale == 1 && inwidth == 1 && sc->width == 1) + { +// fast special case + for (i = 0; i < outcount; i++) + ((signed char *)sc->data)[i] = (int)( (unsigned char)(data[i]) - 128); + } + else + { +// general case + samplefrac = 0; + fracstep = stepscale*256; + for (i = 0; i < outcount; i++) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + if (inwidth == 2) + sample = LittleShort ( ((short *)data)[srcsample] ); + else + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + if (sc->width == 2) + ((short *)sc->data)[i] = sample; + else + ((signed char *)sc->data)[i] = sample >> 8; + } + } +} + +//============================================================================= + +/* +============== +S_LoadSound +============== +*/ +sfxcache_t *S_LoadSound (sfx_t *s) +{ + char namebuffer[256]; + byte *data; + wavinfo_t info; + int len; + float stepscale; + sfxcache_t *sc; + byte stackbuf[1*1024]; // avoid dirtying the cache heap + +// see if still in memory + sc = (sfxcache_t *) Cache_Check (&s->cache); + if (sc) + return sc; + +// Con_Printf ("S_LoadSound: %x\n", (int)stackbuf); + +// load it in + q_strlcpy(namebuffer, "sound/", sizeof(namebuffer)); + q_strlcat(namebuffer, s->name, sizeof(namebuffer)); + +// Con_Printf ("loading %s\n",namebuffer); + + data = COM_LoadStackFile(namebuffer, stackbuf, sizeof(stackbuf), NULL); + + if (!data) + { + Con_Printf ("Couldn't load %s\n", namebuffer); + return NULL; + } + + info = GetWavinfo (s->name, data, com_filesize); + if (info.channels != 1) + { + Con_Printf ("%s is a stereo sample\n",s->name); + return NULL; + } + + if (info.width != 1 && info.width != 2) + { + Con_Printf("%s is not 8 or 16 bit\n", s->name); + return NULL; + } + + stepscale = (float)info.rate / shm->speed; + len = info.samples / stepscale; + + len = len * info.width * info.channels; + + if (info.samples == 0 || len == 0) + { + Con_Printf("%s has zero samples\n", s->name); + return NULL; + } + + sc = (sfxcache_t *) Cache_Alloc ( &s->cache, len + sizeof(sfxcache_t), s->name); + if (!sc) + return NULL; + + sc->length = info.samples; + sc->loopstart = info.loopstart; + sc->speed = info.rate; + sc->width = info.width; + sc->stereo = info.channels; + + ResampleSfx (s, sc->speed, sc->width, data + info.dataofs); + + return sc; +} + + + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +static byte *data_p; +static byte *iff_end; +static byte *last_chunk; +static byte *iff_data; +static int iff_chunk_len; + +static short GetLittleShort (void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +static int GetLittleLong (void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +static void FindNextChunk (const char *name) +{ + while (1) + { + // Need at least 8 bytes for a chunk + if (last_chunk + 8 >= iff_end) + { + data_p = NULL; + return; + } + + data_p = last_chunk + 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0 || iff_chunk_len > iff_end - data_p) + { + data_p = NULL; + Con_DPrintf2("bad \"%s\" chunk length (%d)\n", name, iff_chunk_len); + return; + } + last_chunk = data_p + ((iff_chunk_len + 1) & ~1); + data_p -= 8; + if (!Q_strncmp((char *)data_p, name, 4)) + return; + } +} + +static void FindChunk (const char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + +#if 0 +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); +} +#endif + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !Q_strncmp((char *)data_p + 8, "WAVE", 4))) + { + Con_Printf("%s missing RIFF/WAVE chunks\n", name); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +#if 0 + DumpChunks (); +#endif + + FindChunk("fmt "); + if (!data_p) + { + Con_Printf("%s is missing fmt chunk\n", name); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != WAV_FORMAT_PCM) + { + Con_Printf("%s is not Microsoft PCM format\n", name); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4 + 2; + i = GetLittleShort(); + if (i != 8 && i != 16) + return info; + info.width = i / 8; + +// get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); + // Con_Printf("loopstart=%d\n", sfx->loopstart); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp((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; + // Con_Printf("looped length: %i\n", i); + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + Con_Printf("%s is missing data chunk\n", name); + return info; + } + + data_p += 4; + samples = GetLittleLong() / info.width; + + if (info.samples) + { + if (samples < info.samples) + Sys_Error ("%s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + diff --git a/source/snd_mikmod.c b/source/snd_mikmod.c new file mode 100644 index 0000000..2327181 --- /dev/null +++ b/source/snd_mikmod.c @@ -0,0 +1,204 @@ +/* + * tracker music (module file) decoding support using libmikmod + * Copyright (C) 2013 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_MIKMOD) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mikmod.h" +#include + +#if ((LIBMIKMOD_VERSION+0) < 0x030105) +#error libmikmod version is way too old and unusable. +#endif +#if (LIBMIKMOD_VERSION < 0x030107) /* ancient libmikmod */ +#define S_MIKMOD_initlib(c) MikMod_Init() +#else +#define S_MIKMOD_initlib(c) MikMod_Init(c) +#endif + +#ifndef DMODE_NOISEREDUCTION +#define DMODE_NOISEREDUCTION 0x1000 /* Low pass filtering */ +#endif +#ifndef DMODE_SIMDMIXER +#define DMODE_SIMDMIXER 0x0800 /* enable SIMD mixing */ +#endif + +typedef struct _mik_priv { +/* struct MREADER in libmikmod <= 3.2.0-beta2 + * doesn't have iobase members. adding them here + * so that if we compile against 3.2.0-beta2, we + * can still run OK against 3.2.0b3 and newer. */ + struct MREADER reader; + long iobase, prev_iobase; + fshandle_t *fh; + MODULE *module; +} mik_priv_t; + +static int MIK_Seek (MREADER *r, long ofs, int whence) +{ + return FS_fseek(((mik_priv_t *)r)->fh, ofs, whence); +} + +static long MIK_Tell (MREADER *r) +{ + return FS_ftell(((mik_priv_t *)r)->fh); +} + +static BOOL MIK_Read (MREADER *r, void *ptr, size_t siz) +{ + return !!FS_fread(ptr, siz, 1, ((mik_priv_t *)r)->fh); +} + +static int MIK_Get (MREADER *r) +{ + return FS_fgetc(((mik_priv_t *)r)->fh); +} + +static BOOL MIK_Eof (MREADER *r) +{ + return FS_feof(((mik_priv_t *)r)->fh); +} + +static qboolean S_MIKMOD_CodecInitialize (void) +{ + if (mikmod_codec.initialized) + return true; + + /* set mode flags to only we like: */ + md_mode = 0; + if ((shm->samplebits / 8) == 2) + md_mode |= DMODE_16BITS; + if (shm->channels == 2) + md_mode |= DMODE_STEREO; + md_mode |= DMODE_SOFT_MUSIC; /* this is a software-only mixer */ + + /* md_mixfreq is UWORD, so something like 96000 isn't OK */ + md_mixfreq = (shm->speed < 65536)? shm->speed : 48000; + + /* keeping md_device as 0 which is default (auto-detect: we + * only register drv_nos, and it will be the only one found.) + * md_pansep (stereo channels separation) default 128 is OK. + * no reverbation (md_reverb 0 (up to 15)) is OK. + * md_musicvolume and md_sndfxvolume defaults are 128: OK. */ + /* just tone down overall volume md_volume from 128 to 96? */ + md_volume = 96; + + MikMod_RegisterDriver(&drv_nos); /* only need the "nosound" driver, none else */ + MikMod_RegisterAllLoaders(); + if (S_MIKMOD_initlib(NULL)) + { + Con_DPrintf("Could not initialize MikMod: %s\n", MikMod_strerror(MikMod_errno)); + return false; + } + + /* this can't get set with drv_nos, but whatever, be safe: */ + md_mode &= ~DMODE_SIMDMIXER; /* SIMD mixer is buggy when combined with HQMIXER */ + + mikmod_codec.initialized = true; + return true; +} + +static void S_MIKMOD_CodecShutdown (void) +{ + if (mikmod_codec.initialized) + { + mikmod_codec.initialized = false; + MikMod_Exit(); + } +} + +static qboolean S_MIKMOD_CodecOpenStream (snd_stream_t *stream) +{ + mik_priv_t *priv; + + stream->priv = Z_Malloc(sizeof(mik_priv_t)); + priv = (mik_priv_t *) stream->priv; + priv->reader.Seek = MIK_Seek; + priv->reader.Tell = MIK_Tell; + priv->reader.Read = MIK_Read; + priv->reader.Get = MIK_Get; + priv->reader.Eof = MIK_Eof; + priv->fh = &stream->fh; + + priv->module = Player_LoadGeneric((MREADER *)stream->priv, 64, 0); + if (!priv->module) + { + Con_DPrintf("Could not load module: %s\n", MikMod_strerror(MikMod_errno)); + Z_Free(stream->priv); + return false; + } + + /* keep default values of fadeout (0: don't fade out volume during when last + * position of the module is being played), extspd (1: do process Protracker + * extended speed effect), panflag (1: do process panning effects), wrap (0: + * don't wrap to restart position when module is finished) are OK with us as + * set internally by libmikmod::Player_Init(). */ + /* just change the loop setting to 0, i.e. don't process in-module loops: */ + priv->module->loop = 0; + Player_Start(priv->module); + + stream->info.rate = md_mixfreq; + stream->info.bits = (md_mode & DMODE_16BITS)? 16: 8; + stream->info.width = stream->info.bits / 8; + stream->info.channels = (md_mode & DMODE_STEREO)? 2 : 1; +/* Con_DPrintf("Playing %s (%d chn)\n", priv->module->songname, priv->module->numchn);*/ + + return true; +} + +static int S_MIKMOD_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + if (!Player_Active()) + return 0; + return (int) VC_WriteBytes((SBYTE *)buffer, bytes); +} + +static void S_MIKMOD_CodecCloseStream (snd_stream_t *stream) +{ + Player_Stop(); + Player_Free(((mik_priv_t *)stream->priv)->module); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MIKMOD_CodecRewindStream (snd_stream_t *stream) +{ + Player_SetPosition (0); + return 0; +} + +snd_codec_t mikmod_codec = +{ + CODECTYPE_MOD, + false, + "s3m", + S_MIKMOD_CodecInitialize, + S_MIKMOD_CodecShutdown, + S_MIKMOD_CodecOpenStream, + S_MIKMOD_CodecReadStream, + S_MIKMOD_CodecRewindStream, + S_MIKMOD_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MIKMOD */ + diff --git a/source/snd_mikmod.h b/source/snd_mikmod.h new file mode 100644 index 0000000..9f0338b --- /dev/null +++ b/source/snd_mikmod.h @@ -0,0 +1,13 @@ +/* module tracker decoding support using libmikmod */ + +#if !defined(_SND_MIKMOD_H_) +#define _SND_MIKMOD_H_ + +#if defined(USE_CODEC_MIKMOD) + +extern snd_codec_t mikmod_codec; + +#endif /* USE_CODEC_MIKMOD */ + +#endif /* ! _SND_MIKMOD_H_ */ + diff --git a/source/snd_mix.c b/source/snd_mix.c new file mode 100644 index 0000000..8dc3e02 --- /dev/null +++ b/source/snd_mix.c @@ -0,0 +1,527 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2011 O. Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "quakedef.h" + +#define PAINTBUFFER_SIZE 2048 +portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +int snd_scaletable[32][256]; +int *snd_p, snd_linear_count; +short *snd_out; + +static int snd_vol; + +static void Snd_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i = 0; i < snd_linear_count; i += 2) + { + val = snd_p[i] / 256; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i] = (short)0x8000; + else + snd_out[i] = val; + + val = snd_p[i+1] / 256; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i+1] = (short)0x8000; + else + snd_out[i+1] = val; + } +} + +static void S_TransferStereo16 (int endtime) +{ + int lpos; + int lpaintedtime; + + snd_p = (int *) paintbuffer; + lpaintedtime = paintedtime; + + while (lpaintedtime < endtime) + { + // handle recirculating buffer issues + lpos = lpaintedtime & ((shm->samples >> 1) - 1); + + snd_out = (short *)shm->buffer + (lpos << 1); + + snd_linear_count = (shm->samples >> 1) - lpos; + if (lpaintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - lpaintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + Snd_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + lpaintedtime += (snd_linear_count >> 1); + } +} + +static void S_TransferPaintBuffer (int endtime) +{ + int out_idx, out_mask; + int count, step, val; + int *p; + + if (shm->samplebits == 16 && shm->channels == 2) + { + S_TransferStereo16 (endtime); + return; + } + + p = (int *) paintbuffer; + count = (endtime - paintedtime) * shm->channels; + out_mask = shm->samples - 1; + out_idx = paintedtime * shm->channels & out_mask; + step = 3 - shm->channels; + + if (shm->samplebits == 16) + { + short *out = (short *)shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (shm->samplebits == 8 && !shm->signed8) + { + unsigned char *out = shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val / 256) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (shm->samplebits == 8) /* S8 format, e.g. with Amiga AHI */ + { + signed char *out = (signed char *) shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val / 256); + out_idx = (out_idx + 1) & out_mask; + } + } +} + +/* +============== +S_MakeBlackmanWindowKernel + +Makes a lowpass filter kernel, from equation 16-4 in +"The Scientist and Engineer's Guide to Digital Signal Processing" + +M is the kernel size (not counting the center point), must be even +kernel has room for M+1 floats +f_c is the filter cutoff frequency, as a fraction of the samplerate +============== +*/ +static void S_MakeBlackmanWindowKernel(float *kernel, int M, float f_c) +{ + int i; + for (i = 0; i <= M; i++) + { + if (i == M/2) + { + kernel[i] = 2 * M_PI * f_c; + } + else + { + kernel[i] = ( sin(2 * M_PI * f_c * (i - M/2.0)) / (i - (M/2.0)) ) + * (0.42 - 0.5*cos(2 * M_PI * i / (double)M) + + 0.08*cos(4 * M_PI * i / (double)M) ); + } + } + +// normalize the kernel so all of the values sum to 1 + { + float sum = 0; + for (i = 0; i <= M; i++) + { + sum += kernel[i]; + } + + for (i = 0; i <= M; i++) + { + kernel[i] /= sum; + } + } +} + +typedef struct { + float *memory; // kernelsize floats + float *kernel; // kernelsize floats + int kernelsize; // M+1, rounded up to be a multiple of 16 + int M; // M value used to make kernel, even + int parity; // 0-3 + float f_c; // cutoff frequency, [0..1], fraction of sample rate +} filter_t; + +static void S_UpdateFilter(filter_t *filter, int M, float f_c) +{ + if (filter->f_c != f_c || filter->M != M) + { + if (filter->memory != NULL) free(filter->memory); + if (filter->kernel != NULL) free(filter->kernel); + + filter->M = M; + filter->f_c = f_c; + + filter->parity = 0; + // M + 1 rounded up to the next multiple of 16 + filter->kernelsize = (M + 1) + 16 - ((M + 1) % 16); + filter->memory = (float *) calloc(filter->kernelsize, sizeof(float)); + filter->kernel = (float *) calloc(filter->kernelsize, sizeof(float)); + + S_MakeBlackmanWindowKernel(filter->kernel, M, f_c); + } +} + +/* +============== +S_ApplyFilter + +Lowpass-filter the given buffer containing 44100Hz audio. + +As an optimization, it decimates the audio to 11025Hz (setting every sample +position that's not a multiple of 4 to 0), then convoluting with the filter +kernel is 4x faster, because we can skip 3/4 of the input samples that are +known to be 0 and skip 3/4 of the filter kernel. +============== +*/ +static void S_ApplyFilter(filter_t *filter, int *data, int stride, int count) +{ + int i, j; + float *input; + const int kernelsize = filter->kernelsize; + const float *kernel = filter->kernel; + int parity; + + input = (float *) malloc(sizeof(float) * (filter->kernelsize + count)); + +// set up the input buffer +// memory holds the previous filter->kernelsize samples of input. + memcpy(input, filter->memory, filter->kernelsize * sizeof(float)); + + for (i=0; ikernelsize+i] = data[i * stride] / (32768.0 * 256.0); + } + +// copy out the last filter->kernelsize samples to 'memory' for next time + memcpy(filter->memory, input + count, filter->kernelsize * sizeof(float)); + +// apply the filter + parity = filter->parity; + + for (i=0; iparity = parity; + + free(input); +} + +/* +============== +S_LowpassFilter + +lowpass filters 24-bit integer samples in 'data' (stored in 32-bit ints). +assumes 44100Hz sample rate, and lowpasses at around 5kHz +memory should be a zero-filled filter_t struct +============== +*/ +static void S_LowpassFilter(int *data, int stride, int count, + filter_t *memory) +{ + int M; + float bw, f_c; + + switch ((int)snd_filterquality.value) + { + case 1: + M = 126; bw = 0.900; break; + case 2: + M = 150; bw = 0.915; break; + case 3: + M = 174; bw = 0.930; break; + case 4: + M = 198; bw = 0.945; break; + case 5: + default: + M = 222; bw = 0.960; break; + } + + f_c = (bw * 11025 / 2.0) / 44100.0; + + S_UpdateFilter(memory, M, f_c); + S_ApplyFilter(memory, data, stride, count); +} + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +static void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int endtime, int paintbufferstart); +static void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int endtime, int paintbufferstart); + +void S_PaintChannels (int endtime) +{ + int i; + int end, ltime, count; + channel_t *ch; + sfxcache_t *sc; + + snd_vol = sfxvolume.value * 256; + + while (paintedtime < endtime) + { + // if paintbuffer is smaller than DMA buffer + end = endtime; + if (endtime - paintedtime > PAINTBUFFER_SIZE) + end = paintedtime + PAINTBUFFER_SIZE; + + // clear the paint buffer + memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t)); + + // paint in the channels. + ch = snd_channels; + for (i = 0; i < total_channels; i++, ch++) + { + if (!ch->sfx) + continue; + if (!ch->leftvol && !ch->rightvol) + continue; + sc = S_LoadSound (ch->sfx); + if (!sc) + continue; + + ltime = paintedtime; + + while (ltime < end) + { // paint up to end + if (ch->end < end) + count = ch->end - ltime; + else + count = end - ltime; + + if (count > 0) + { + // the last param to SND_PaintChannelFrom is the index + // to start painting to in the paintbuffer, usually 0. + if (sc->width == 1) + SND_PaintChannelFrom8(ch, sc, count, ltime - paintedtime); + else + SND_PaintChannelFrom16(ch, sc, count, ltime - paintedtime); + + ltime += count; + } + + // if at end of loop, restart + if (ltime >= ch->end) + { + if (sc->loopstart >= 0) + { + ch->pos = sc->loopstart; + ch->end = ltime + sc->length - ch->pos; + } + else + { // channel just stopped + ch->sfx = NULL; + break; + } + } + } + } + + // clip each sample to 0dB, then reduce by 6dB (to leave some headroom for + // the lowpass filter and the music). the lowpass will smooth out the + // clipping + for (i=0; ispeed == 44100) + { + static filter_t memory_l, memory_r; + S_LowpassFilter((int *)paintbuffer, 2, end - paintedtime, &memory_l); + S_LowpassFilter(((int *)paintbuffer) + 1, 2, end - paintedtime, &memory_r); + } + + // paint in the music + if (s_rawend >= paintedtime) + { // copy from the streaming sound source + int s; + int stop; + + stop = (end < s_rawend) ? end : s_rawend; + + for (i = paintedtime; i < stop; i++) + { + s = i & (MAX_RAW_SAMPLES - 1); + // lower music by 6db to match sfx + paintbuffer[i - paintedtime].left += s_rawsamples[s].left / 2; + paintbuffer[i - paintedtime].right += s_rawsamples[s].right / 2; + } + // if (i != end) + // Con_Printf ("partial stream\n"); + // else + // Con_Printf ("full stream\n"); + } + + // transfer out according to DMA format + S_TransferPaintBuffer(end); + paintedtime = end; + } +} + +void SND_InitScaletable (void) +{ + int i, j; + int scale; + + for (i = 0; i < 32; i++) + { + scale = i * 8 * 256 * sfxvolume.value; + for (j = 0; j < 256; j++) + { + /* When compiling with gcc-4.1.0 at optimisations O1 and + higher, the tricky signed char type conversion is not + guaranteed. Therefore we explicity calculate the signed + value from the index as required. From Kevin Shanahan. + See: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=26719 + */ + // snd_scaletable[i][j] = ((signed char)j) * scale; + snd_scaletable[i][j] = ((j < 128) ? j : j - 256) * scale; + } + } +} + + +static void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int paintbufferstart) +{ + int data; + int *lscale, *rscale; + unsigned char *sfx; + int i; + + if (ch->leftvol > 255) + ch->leftvol = 255; + if (ch->rightvol > 255) + ch->rightvol = 255; + + lscale = snd_scaletable[ch->leftvol >> 3]; + rscale = snd_scaletable[ch->rightvol >> 3]; + sfx = (unsigned char *)sc->data + ch->pos; + + for (i = 0; i < count; i++) + { + data = sfx[i]; + paintbuffer[paintbufferstart + i].left += lscale[data]; + paintbuffer[paintbufferstart + i].right += rscale[data]; + } + + ch->pos += count; +} + +static void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count, int paintbufferstart) +{ + int data; + int left, right; + int leftvol, rightvol; + signed short *sfx; + int i; + + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + leftvol /= 256; + rightvol /= 256; + sfx = (signed short *)sc->data + ch->pos; + + for (i = 0; i < count; i++) + { + data = sfx[i]; + // this was causing integer overflow as observed in quakespasm + // with the warpspasm mod moved >>8 to left/right volume above. + // left = (data * leftvol) >> 8; + // right = (data * rightvol) >> 8; + left = data * leftvol; + right = data * rightvol; + paintbuffer[paintbufferstart + i].left += left; + paintbuffer[paintbufferstart + i].right += right; + } + + ch->pos += count; +} + diff --git a/source/snd_mp3.c b/source/snd_mp3.c new file mode 100644 index 0000000..42ba434 --- /dev/null +++ b/source/snd_mp3.c @@ -0,0 +1,587 @@ +/* + * MP3 decoding support using libmad: Adapted from the SoX library at + * http://sourceforge.net/projects/sox/, LGPLv2, Copyright (c) 2007-2009 + * SoX contributors, written by Fabrizio Gennari , + * with the decoding part based on the decoder tutorial program madlld + * written by Bertrand Petit (BSD license, see at + * http://www.bsd-dk.dk/~elrond/audio/madlld/). The tag identification + * functions were adapted from the GPL-licensed libid3tag library, see at + * http://www.underbit.com/products/mad/. Adapted to Quake and Hexen II + * game engines by O.Sezer : + * Copyright (C) 2010-2015 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mp3.h" +#include + +#define ID3_TAG_FLAG_FOOTERPRESENT 0x10 + +/* Under Windows, importing data from DLLs is a dicey proposition. This is true + * when using dlopen, but also true if linking directly against the DLL if the + * header does not mark the data as __declspec(dllexport), which mad.h does not. + * Sidestep the issue by defining our own mad_timer_zero. This is needed because + * mad_timer_zero is used in some of the mad.h macros. + */ +#define mad_timer_zero mad_timer_zero_stub +static mad_timer_t const mad_timer_zero_stub = {0, 0}; + +/* MAD returns values with MAD_F_FRACBITS (28) bits of precision, though it's + not certain that all of them are meaningful. Default to 16 bits to + align with most users expectation of output file should be 16 bits. */ +#define MP3_MAD_SAMPLEBITS 16 +#define MP3_MAD_SAMPLEWIDTH 2 +#define MP3_BUFFER_SIZE (5 * 8192) + +/* Private data */ +typedef struct _mp3_priv_t +{ + unsigned char mp3_buffer[MP3_BUFFER_SIZE]; + struct mad_stream Stream; + struct mad_frame Frame; + struct mad_synth Synth; + mad_timer_t Timer; + ptrdiff_t cursamp; + size_t FrameCount; +} mp3_priv_t; + +/* This function merges the functions tagtype() and id3_tag_query() + * from MAD's libid3tag, so we don't have to link to it + * Returns 0 if the frame is not an ID3 tag, tag length if it is */ +static inline qboolean tag_is_id3v1(const unsigned char *data, size_t length) +{ + if (length >= 3 && + data[0] == 'T' && data[1] == 'A' && data[2] == 'G') + { + return true; + } + return false; +} + +static inline qboolean tag_is_id3v2(const unsigned char *data, size_t length) +{ + if (length >= 10 && + (data[0] == 'I' && data[1] == 'D' && data[2] == '3') && + data[3] < 0xff && data[4] < 0xff && + data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80) + { + return true; + } + return false; +} + +/* http://wiki.hydrogenaud.io/index.php?title=APEv1_specification + * http://wiki.hydrogenaud.io/index.php?title=APEv2_specification + * Detect an APEv2 tag. (APEv1 has no header, so no luck.) + */ +static inline qboolean tag_is_apetag(const unsigned char *data, size_t length) +{ + unsigned int v; + + if (length < 32) return false; + if (memcmp(data,"APETAGEX",8) != 0) + return false; + v = (data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]; + if (v != 2000U/* && v != 1000U*/) + return false; + v = 0; + if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) + return false; + return true; +} + +static size_t mp3_tagsize(const unsigned char *data, size_t length) +{ + size_t size; + + if (tag_is_id3v1(data, length)) + return 128; + + if (tag_is_id3v2(data, length)) + { + unsigned char flags = data[5]; + size = 10 + (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]; + if (flags & ID3_TAG_FLAG_FOOTERPRESENT) + size += 10; + for ( ; size < length && !data[size]; ++size) + ; /* Consume padding */ + return size; + } + + if (tag_is_apetag(data, length)) + { + size = (data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]; + size += 32; + return size; + } + + return 0; +} + +/* Attempts to read an ID3 tag at the current location in stream and + * consume it all. Returns -1 if no tag is found. Its up to caller + * to recover. */ +static int mp3_inputtag(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + int rc = -1; + size_t remaining; + size_t tagsize; + + /* FIXME: This needs some more work if we are to ever + * look at the ID3 frame. This is because the Stream + * may not be able to hold the complete ID3 frame. + * We should consume the whole frame inside tagtype() + * instead of outside of tagframe(). That would support + * recovering when Stream contains less then 8-bytes (header) + * and also when ID3v2 is bigger then Stream buffer size. + * Need to pass in stream so that buffer can be + * consumed as well as letting additional data to be + * read in. + */ + remaining = p->Stream.bufend - p->Stream.next_frame; + tagsize = mp3_tagsize(p->Stream.this_frame, remaining); + if (tagsize != 0) + { + mad_stream_skip(&p->Stream, tagsize); + rc = 0; + } + + /* We know that a valid frame hasn't been found yet + * so help libmad out and go back into frame seek mode. + * This is true whether an ID3 tag was found or not. + */ + mad_stream_sync(&p->Stream); + + return rc; +} + +/* (Re)fill the stream buffer that is to be decoded. If any data + * still exists in the buffer then they are first shifted to be + * front of the stream buffer. */ +static int mp3_inputdata(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t bytes_read; + size_t remaining; + + remaining = p->Stream.bufend - p->Stream.next_frame; + + /* libmad does not consume all the buffer it's given. Some + * data, part of a truncated frame, is left unused at the + * end of the buffer. That data must be put back at the + * beginning of the buffer and taken in account for + * refilling the buffer. This means that the input buffer + * must be large enough to hold a complete frame at the + * highest observable bit-rate (currently 448 kb/s). + * TODO: Is 2016 bytes the size of the largest frame? + * (448000*(1152/32000))/8 + */ + memmove(p->mp3_buffer, p->Stream.next_frame, remaining); + + bytes_read = FS_fread(p->mp3_buffer + remaining, 1, + MP3_BUFFER_SIZE - remaining, &stream->fh); + if (bytes_read == 0) + return -1; + + mad_stream_buffer(&p->Stream, p->mp3_buffer, bytes_read+remaining); + p->Stream.error = MAD_ERROR_NONE; + + return 0; +} + +static int mp3_startread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t ReadSize; + + mad_stream_init(&p->Stream); + mad_frame_init(&p->Frame); + mad_synth_init(&p->Synth); + mad_timer_reset(&p->Timer); + + /* Decode at least one valid frame to find out the input + * format. The decoded frame will be saved off so that it + * can be processed later. + */ + ReadSize = FS_fread(p->mp3_buffer, 1, MP3_BUFFER_SIZE, &stream->fh); + if (!ReadSize || FS_ferror(&stream->fh)) + return -1; + + mad_stream_buffer(&p->Stream, p->mp3_buffer, ReadSize); + + /* Find a valid frame before starting up. This makes sure + * that we have a valid MP3 and also skips past ID3v2 tags + * at the beginning of the audio file. + */ + p->Stream.error = MAD_ERROR_NONE; + while (mad_frame_decode(&p->Frame,&p->Stream)) + { + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + return -1;/* EOF with no valid data */ + + continue; + } + + /* Consume any ID3 tags */ + mp3_inputtag(stream); + + /* FIXME: We should probably detect when we've read + * a bunch of non-ID3 data and still haven't found a + * frame. In that case we can abort early without + * scanning the whole file. + */ + p->Stream.error = MAD_ERROR_NONE; + } + + if (p->Stream.error) + { + Con_Printf("MP3: No valid MP3 frame found\n"); + return -1; + } + + switch(p->Frame.header.mode) + { + case MAD_MODE_SINGLE_CHANNEL: + case MAD_MODE_DUAL_CHANNEL: + case MAD_MODE_JOINT_STEREO: + case MAD_MODE_STEREO: + stream->info.channels = MAD_NCHANNELS(&p->Frame.header); + break; + default: + Con_Printf("MP3: Cannot determine number of channels\n"); + return -1; + } + + p->FrameCount = 1; + + mad_timer_add(&p->Timer,p->Frame.header.duration); + mad_synth_frame(&p->Synth,&p->Frame); + stream->info.rate = p->Synth.pcm.samplerate; + stream->info.bits = MP3_MAD_SAMPLEBITS; + stream->info.width = MP3_MAD_SAMPLEWIDTH; + + p->cursamp = 0; + + return 0; +} + +/* Read up to len samples from p->Synth + * If needed, read some more MP3 data, decode them and synth them + * Place in buf[]. + * Return number of samples read. */ +static int mp3_decode(snd_stream_t *stream, byte *buf, int len) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + int donow, i, done = 0; + mad_fixed_t sample; + int chan, x; + + do + { + x = (p->Synth.pcm.length - p->cursamp) * stream->info.channels; + donow = q_min(len, x); + i = 0; + while (i < donow) + { + for (chan = 0; chan < stream->info.channels; chan++) + { + sample = p->Synth.pcm.samples[chan][p->cursamp]; + /* convert from fixed to short, + * write in host-endian format. */ + if (sample <= -MAD_F_ONE) + sample = -0x7FFF; + else if (sample >= MAD_F_ONE) + sample = 0x7FFF; + else + sample >>= (MAD_F_FRACBITS + 1 - 16); + if (host_bigendian) + { + *buf++ = (sample >> 8) & 0xFF; + *buf++ = sample & 0xFF; + } + else /* assumed LITTLE_ENDIAN. */ + { + *buf++ = sample & 0xFF; + *buf++ = (sample >> 8) & 0xFF; + } + i++; + } + p->cursamp++; + } + + len -= donow; + done += donow; + + if (len == 0) + break; + + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + { + /* check feof() ?? */ + Con_DPrintf("mp3 EOF\n"); + break; + } + } + + if (mad_frame_decode(&p->Frame, &p->Stream)) + { + if (MAD_RECOVERABLE(p->Stream.error)) + { + mp3_inputtag(stream); + continue; + } + else + { + if (p->Stream.error == MAD_ERROR_BUFLEN) + continue; + else + { + Con_Printf("MP3: unrecoverable frame level error (%s)\n", + mad_stream_errorstr(&p->Stream)); + break; + } + } + } + p->FrameCount++; + mad_timer_add(&p->Timer, p->Frame.header.duration); + mad_synth_frame(&p->Synth, &p->Frame); + p->cursamp = 0; + } while (1); + + return done; +} + +static int mp3_stopread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t*) stream->priv; + + mad_synth_finish(&p->Synth); + mad_frame_finish(&p->Frame); + mad_stream_finish(&p->Stream); + + return 0; +} + +static int mp3_madseek(snd_stream_t *stream, unsigned long offset) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t initial_bitrate = p->Frame.header.bitrate; + size_t tagsize = 0, consumed = 0; + int vbr = 0; /* Variable Bit Rate, bool */ + qboolean depadded = false; + unsigned long to_skip_samples = 0; + + /* Reset all */ + FS_rewind(&stream->fh); + mad_timer_reset(&p->Timer); + p->FrameCount = 0; + + /* They where opened in startread */ + mad_synth_finish(&p->Synth); + mad_frame_finish(&p->Frame); + mad_stream_finish(&p->Stream); + + mad_stream_init(&p->Stream); + mad_frame_init(&p->Frame); + mad_synth_init(&p->Synth); + + offset /= stream->info.channels; + to_skip_samples = offset; + + while (1) /* Read data from the MP3 file */ + { + int bytes_read, padding = 0; + size_t leftover = p->Stream.bufend - p->Stream.next_frame; + + memcpy(p->mp3_buffer, p->Stream.this_frame, leftover); + bytes_read = FS_fread(p->mp3_buffer + leftover, (size_t) 1, + MP3_BUFFER_SIZE - leftover, &stream->fh); + if (bytes_read <= 0) + { + Con_DPrintf("seek failure. unexpected EOF (frames=%lu leftover=%lu)\n", + (unsigned long)p->FrameCount, (unsigned long)leftover); + break; + } + for ( ; !depadded && padding < bytes_read && !p->mp3_buffer[padding]; ++padding) + ; + depadded = true; + mad_stream_buffer(&p->Stream, p->mp3_buffer + padding, leftover + bytes_read - padding); + + while (1) /* Decode frame headers */ + { + static unsigned short samples; + p->Stream.error = MAD_ERROR_NONE; + + /* Not an audio frame */ + if (mad_header_decode(&p->Frame.header, &p->Stream) == -1) + { + if (p->Stream.error == MAD_ERROR_BUFLEN) + break; /* Normal behaviour; get some more data from the file */ + if (!MAD_RECOVERABLE(p->Stream.error)) + { + Con_DPrintf("unrecoverable MAD error\n"); + break; + } + if (p->Stream.error == MAD_ERROR_LOSTSYNC) + { + unsigned long available = (p->Stream.bufend - p->Stream.this_frame); + tagsize = mp3_tagsize(p->Stream.this_frame, (size_t) available); + if (tagsize) + { /* It's some ID3 tags, so just skip */ + if (tagsize >= available) + { + FS_fseek(&stream->fh, (long)(tagsize - available), SEEK_CUR); + depadded = false; + } + mad_stream_skip(&p->Stream, q_min(tagsize, available)); + } + else + { + Con_DPrintf("MAD lost sync\n"); + } + } + else + { + Con_DPrintf("recoverable MAD error\n"); + } + continue; + } + + consumed += p->Stream.next_frame - p->Stream.this_frame; + vbr |= (p->Frame.header.bitrate != initial_bitrate); + + samples = 32 * MAD_NSBSAMPLES(&p->Frame.header); + + p->FrameCount++; + mad_timer_add(&p->Timer, p->Frame.header.duration); + + if (to_skip_samples <= samples) + { + mad_frame_decode(&p->Frame,&p->Stream); + mad_synth_frame(&p->Synth, &p->Frame); + p->cursamp = to_skip_samples; + return 0; + } + else to_skip_samples -= samples; + + /* If not VBR, we can extrapolate frame size */ + if (p->FrameCount == 64 && !vbr) + { + p->FrameCount = offset / samples; + to_skip_samples = offset % samples; + if (0 != FS_fseek(&stream->fh, (p->FrameCount * consumed / 64) + tagsize, SEEK_SET)) + return -1; + + /* Reset Stream for refilling buffer */ + mad_stream_finish(&p->Stream); + mad_stream_init(&p->Stream); + break; + } + } + } + + return -1; +} + +static qboolean S_MP3_CodecInitialize (void) +{ + return true; +} + +static void S_MP3_CodecShutdown (void) +{ +} + +static qboolean S_MP3_CodecOpenStream (snd_stream_t *stream) +{ + int err; + + stream->priv = calloc(1, sizeof(mp3_priv_t)); + if (!stream->priv) + { + Con_Printf("Insufficient memory for MP3 audio\n"); + return false; + } + err = mp3_startread(stream); + if (err != 0) + { + Con_Printf("%s is not a valid mp3 file\n", stream->name); + } + else if (stream->info.channels != 1 && stream->info.channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + stream->info.channels, stream->name); + } + else + { + return true; + } + free(stream->priv); + return false; +} + +static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int res = mp3_decode(stream, (byte *)buffer, bytes / stream->info.width); + return res * stream->info.width; +} + +static void S_MP3_CodecCloseStream (snd_stream_t *stream) +{ + mp3_stopread(stream); + free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MP3_CodecRewindStream (snd_stream_t *stream) +{ + /* + mp3_stopread(stream); + FS_rewind(&stream->fh); + return mp3_startread(stream); + */ + return mp3_madseek(stream, 0); +} + +snd_codec_t mp3_codec = +{ + CODECTYPE_MP3, + true, /* always available. */ + "mp3", + S_MP3_CodecInitialize, + S_MP3_CodecShutdown, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecRewindStream, + S_MP3_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MP3 */ + diff --git a/source/snd_mp3.h b/source/snd_mp3.h new file mode 100644 index 0000000..d54ae4d --- /dev/null +++ b/source/snd_mp3.h @@ -0,0 +1,13 @@ +/* MP3 decoding support using libmad or libmpg123. */ + +#if !defined(_SND_MP3_H_) +#define _SND_MP3_H_ + +#if defined(USE_CODEC_MP3) + +extern snd_codec_t mp3_codec; + +#endif /* USE_CODEC_MP3 */ + +#endif /* ! _SND_MP3_H_ */ + diff --git a/source/snd_mpg123.c b/source/snd_mpg123.c new file mode 100644 index 0000000..7b866ca --- /dev/null +++ b/source/snd_mpg123.c @@ -0,0 +1,226 @@ +/* + * MP3 decoding support using libmpg123, loosely based on an SDL_mixer + * See: http://bubu.lv/changeset/4/public/libs/SDL/generated/SDL_mixer + * + * Copyright (C) 2011-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mp3.h" +#include +#include + +#if !defined(MPG123_API_VERSION) || (MPG123_API_VERSION < 24) +#error minimum required libmpg123 version is 1.12.0 (api version 24) +#endif /* MPG123_API_VERSION */ + +/* Private data */ +typedef struct _mp3_priv_t +{ + int handle_newed, handle_opened; + mpg123_handle* handle; +} mp3_priv_t; + +/* CALLBACK FUNCTIONS: */ +/* CAREFUL: libmpg123 expects POSIX read() and lseek() behavior, + * however our FS_fread() and FS_fseek() return fread() and fseek() + * compatible values. */ + +static ssize_t mp3_read (void *f, void *buf, size_t size) +{ + ssize_t ret = (ssize_t) FS_fread(buf, 1, size, (fshandle_t *)f); + if (ret == 0 && errno != 0) + ret = -1; + return ret; +} + +static off_t mp3_seek (void *f, off_t offset, int whence) +{ + if (f == NULL) return (-1); + if (FS_fseek((fshandle_t *)f, (long) offset, whence) < 0) + return (off_t)-1; + return (off_t) FS_ftell((fshandle_t *)f); +} + +static qboolean S_MP3_CodecInitialize (void) +{ + if (!mp3_codec.initialized) + { + if (mpg123_init() != MPG123_OK) + { + Con_Printf ("Could not initialize mpg123\n"); + return false; + } + mp3_codec.initialized = true; + return true; + } + + return true; +} + +static void S_MP3_CodecShutdown (void) +{ + if (mp3_codec.initialized) + { + mp3_codec.initialized = false; + mpg123_exit(); + } +} + +static qboolean S_MP3_CodecOpenStream (snd_stream_t *stream) +{ + long rate = 0; + int encoding = 0, channels = 0; + mp3_priv_t *priv = NULL; + + stream->priv = Z_Malloc(sizeof(mp3_priv_t)); + priv = (mp3_priv_t *) stream->priv; + priv->handle = mpg123_new(NULL, NULL); + if (priv->handle == NULL) + { + Con_Printf("Unable to allocate mpg123 handle\n"); + goto _fail; + } + priv->handle_newed = 1; + + if (mpg123_replace_reader_handle(priv->handle, mp3_read, mp3_seek, NULL) != MPG123_OK || + mpg123_open_handle(priv->handle, &stream->fh) != MPG123_OK) + { + Con_Printf("Unable to open mpg123 handle\n"); + goto _fail; + } + priv->handle_opened = 1; + + if (mpg123_getformat(priv->handle, &rate, &channels, &encoding) != MPG123_OK) + { + Con_Printf("Unable to retrieve mpg123 format for %s\n", stream->name); + goto _fail; + } + + switch (channels) + { + case MPG123_MONO: + stream->info.channels = 1; + break; + case MPG123_STEREO: + stream->info.channels = 2; + break; + default: + Con_Printf("Unsupported number of channels %d in %s\n", channels, stream->name); + goto _fail; + } + + stream->info.rate = rate; + + switch (encoding) + { + case MPG123_ENC_UNSIGNED_8: + stream->info.bits = 8; + stream->info.width = 1; + break; + case MPG123_ENC_SIGNED_8: + /* unsupported: force mpg123 to convert */ + stream->info.bits = 8; + stream->info.width = 1; + encoding = MPG123_ENC_UNSIGNED_8; + break; + case MPG123_ENC_SIGNED_16: + stream->info.bits = 16; + stream->info.width = 2; + break; + case MPG123_ENC_UNSIGNED_16: + default: + /* unsupported: force mpg123 to convert */ + stream->info.bits = 16; + stream->info.width = 2; + encoding = MPG123_ENC_SIGNED_16; + break; + } + if (mpg123_format_support(priv->handle, rate, encoding) == 0) + { + Con_Printf("Unsupported format for %s\n", stream->name); + goto _fail; + } + mpg123_format_none(priv->handle); + mpg123_format(priv->handle, rate, channels, encoding); + + return true; +_fail: + if (priv) + { + if (priv->handle_opened) + mpg123_close(priv->handle); + if (priv->handle_newed) + mpg123_delete(priv->handle); + Z_Free(stream->priv); + } + return false; +} + +static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + size_t bytes_read = 0; + int res = mpg123_read (priv->handle, (unsigned char *)buffer, (size_t)bytes, &bytes_read); + switch (res) + { + case MPG123_DONE: + Con_DPrintf("mp3 EOF\n"); + case MPG123_OK: + return (int)bytes_read; + } + return -1; /* error */ +} + +static void S_MP3_CodecCloseStream (snd_stream_t *stream) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + mpg123_close(priv->handle); + mpg123_delete(priv->handle); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MP3_CodecRewindStream (snd_stream_t *stream) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + off_t res = mpg123_seek(priv->handle, 0, SEEK_SET); + if (res >= 0) return (0); + return res; +} + +snd_codec_t mp3_codec = +{ + CODECTYPE_MP3, + false, + "mp3", + S_MP3_CodecInitialize, + S_MP3_CodecShutdown, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecRewindStream, + S_MP3_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MP3 */ + diff --git a/source/snd_opus.c b/source/snd_opus.c new file mode 100644 index 0000000..a2148e9 --- /dev/null +++ b/source/snd_opus.c @@ -0,0 +1,210 @@ +/* + * Ogg/Opus streaming music support, loosely based on several open source + * Quake engine based projects with many modifications. + * + * Copyright (C) 2012-2013 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_OPUS) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_opus.h" + +#include +#include + + +/* CALLBACK FUNCTIONS: */ + +static int opc_fclose (void *f) +{ + return 0; /* we fclose() elsewhere. */ +} + +static int opc_fread (void *f, unsigned char *buf, int size) +{ + int ret; + + if (size < 0) + { + errno = EINVAL; + return -1; + } + + ret = (int) FS_fread(buf, 1, (size_t)size, (fshandle_t *)f); + if (ret == 0 && errno != 0) + ret = -1; + return ret; +} + +static int opc_fseek (void *f, opus_int64 off, int whence) +{ + if (f == NULL) return (-1); + return FS_fseek((fshandle_t *)f, (long) off, whence); +} + +static opus_int64 opc_ftell (void *f) +{ + return (opus_int64) FS_ftell((fshandle_t *)f); +} + +static const OpusFileCallbacks opc_qfs = +{ + (int (*)(void *, unsigned char *, int)) opc_fread, + (int (*)(void *, opus_int64, int)) opc_fseek, + (opus_int64 (*)(void *)) opc_ftell, + (int (*)(void *)) opc_fclose +}; + +static qboolean S_OPUS_CodecInitialize (void) +{ + return true; +} + +static void S_OPUS_CodecShutdown (void) +{ +} + +static qboolean S_OPUS_CodecOpenStream (snd_stream_t *stream) +{ + OggOpusFile *opFile; + const OpusHead *op_info; + long numstreams; + int res; + + opFile = op_open_callbacks(&stream->fh, &opc_qfs, NULL, 0, &res); + if (!opFile) + { + Con_Printf("%s is not a valid Opus file (error %i).\n", + stream->name, res); + goto _fail; + } + + stream->priv = opFile; + + if (!op_seekable(opFile)) + { + Con_Printf("Opus stream %s not seekable.\n", stream->name); + goto _fail; + } + + op_info = op_head(opFile, -1); + if (!op_info) + { + Con_Printf("Unable to get stream information for %s.\n", stream->name); + goto _fail; + } + + /* FIXME: handle section changes */ + numstreams = op_info->stream_count; + if (numstreams != 1) + { + Con_Printf("More than one (%ld) stream in %s\n", + (long)op_info->stream_count, stream->name); + goto _fail; + } + + if (op_info->channel_count != 1 && op_info->channel_count != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + op_info->channel_count, stream->name); + goto _fail; + } + + /* All Opus audio is coded at 48 kHz, and should also be decoded + * at 48 kHz for playback: info->input_sample_rate only tells us + * the sampling rate of the original input before opus encoding. + * S_RawSamples() shall already downsample this, as necessary. */ + stream->info.rate = 48000; + stream->info.channels = op_info->channel_count; + /* op_read() yields 16-bit output using native endian ordering: */ + stream->info.bits = 16; + stream->info.width = 2; + + return true; +_fail: + if (opFile) + op_free(opFile); + return false; +} + +static int S_OPUS_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int section; /* FIXME: handle section changes */ + int cnt, res, rem; + opus_int16 * ptr; + + rem = bytes / stream->info.width; + if (rem / stream->info.channels <= 0) + return 0; + + cnt = 0; + ptr = (opus_int16 *) buffer; + while (1) + { + /* op_read() yields 16-bit output using native endian ordering. returns + * the number of samples read per channel on success, or a negative value + * on failure. */ + res = op_read((OggOpusFile *)stream->priv, ptr, rem, §ion); + if (res <= 0) + break; + cnt += res; + res *= stream->info.channels; + rem -= res; + if (rem <= 0) + break; + ptr += res; + } + + if (res < 0) + return res; + + cnt *= (stream->info.channels * stream->info.width); + return cnt; +} + +static void S_OPUS_CodecCloseStream (snd_stream_t *stream) +{ + op_free((OggOpusFile *)stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_OPUS_CodecRewindStream (snd_stream_t *stream) +{ + return op_pcm_seek ((OggOpusFile *)stream->priv, 0); +} + +snd_codec_t opus_codec = +{ + CODECTYPE_OPUS, + true, /* always available. */ + "opus", + S_OPUS_CodecInitialize, + S_OPUS_CodecShutdown, + S_OPUS_CodecOpenStream, + S_OPUS_CodecReadStream, + S_OPUS_CodecRewindStream, + S_OPUS_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_OPUS */ + diff --git a/source/snd_opus.h b/source/snd_opus.h new file mode 100644 index 0000000..4c2a25f --- /dev/null +++ b/source/snd_opus.h @@ -0,0 +1,13 @@ +/* Ogg/Opus streaming music support. */ + +#if !defined(_SND_OPUS_H_) +#define _SND_OPUS_H_ 1 + +#if defined(USE_CODEC_OPUS) + +extern snd_codec_t opus_codec; + +#endif /* USE_CODEC_OPUS */ + +#endif /* ! _SND_OPUS_H_ */ + diff --git a/source/snd_sdl.c b/source/snd_sdl.c new file mode 100644 index 0000000..1ceff1d --- /dev/null +++ b/source/snd_sdl.c @@ -0,0 +1,213 @@ +/* + * snd_sdl.c - SDL audio driver for Hexen II: Hammer of Thyrion (uHexen2) + * based on implementations found in the quakeforge and ioquake3 projects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005-2012 O.Sezer + * Copyright (C) 2010-2014 QuakeSpasm developers + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#include + +static int buffersize; + + +static void SDLCALL paint_audio (void *unused, Uint8 *stream, int len) +{ + int pos, tobufend; + int len1, len2; + + if (!shm) + { /* shouldn't happen, but just in case */ + memset(stream, 0, len); + return; + } + + pos = (shm->samplepos * (shm->samplebits / 8)); + if (pos >= buffersize) + shm->samplepos = pos = 0; + + tobufend = buffersize - pos; /* bytes to buffer's end. */ + len1 = len; + len2 = 0; + + if (len1 > tobufend) + { + len1 = tobufend; + len2 = len - len1; + } + + memcpy(stream, shm->buffer + pos, len1); + + if (len2 <= 0) + { + shm->samplepos += (len1 / (shm->samplebits / 8)); + } + else + { /* wraparound? */ + memcpy(stream + len1, shm->buffer, len2); + shm->samplepos = (len2 / (shm->samplebits / 8)); + } + + if (shm->samplepos >= buffersize) + shm->samplepos = 0; +} + +qboolean SNDDMA_Init (dma_t *dma) +{ + SDL_AudioSpec desired, obtained; + int tmp, val; + char drivername[128]; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + Con_Printf("Couldn't init SDL audio: %s\n", SDL_GetError()); + return false; + } + + /* Set up the desired format */ + desired.freq = tmp = snd_mixspeed.value; + desired.format = (loadas8bit.value) ? AUDIO_U8 : AUDIO_S16SYS; + desired.channels = 2; /* = desired_channels; */ + if (desired.freq <= 11025) + desired.samples = 256; + else if (desired.freq <= 22050) + desired.samples = 512; + else if (desired.freq <= 44100) + desired.samples = 1024; + else if (desired.freq <= 56000) + desired.samples = 2048; /* for 48 kHz */ + else + desired.samples = 4096; /* for 96 kHz */ + desired.callback = paint_audio; + desired.userdata = NULL; + + /* Open the audio device */ + if (SDL_OpenAudio(&desired, &obtained) == -1) + { + Con_Printf("Couldn't open SDL audio: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + /* Make sure we can support the audio format */ + switch (obtained.format) + { + case AUDIO_S8: /* maybe needed by AHI */ + case AUDIO_U8: + case AUDIO_S16SYS: + /* Supported */ + break; + default: + Con_Printf ("Unsupported audio format received (%u)\n", obtained.format); + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + memset ((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + /* Fill the audio DMA information block */ + shm->samplebits = (obtained.format & 0xFF); /* first byte of format is bits */ + shm->signed8 = (obtained.format == AUDIO_S8); + if (obtained.freq != tmp) + Con_Printf ("Warning: Rate set (%d) didn't match requested rate (%d)!\n", obtained.freq, tmp); + shm->speed = obtained.freq; + shm->channels = obtained.channels; + tmp = (obtained.samples * obtained.channels) * 10; + if (tmp & (tmp - 1)) + { /* make it a power of two */ + val = 1; + while (val < tmp) + val <<= 1; + + tmp = val; + } + shm->samples = tmp; + shm->samplepos = 0; + shm->submission_chunk = 1; + + Con_Printf ("SDL audio spec : %d Hz, %d samples, %d channels\n", + obtained.freq, obtained.samples, obtained.channels); + { + const char *driver = SDL_GetCurrentAudioDriver(); + const char *device = SDL_GetAudioDeviceName(0, SDL_FALSE); + q_snprintf(drivername, sizeof(drivername), "%s - %s", + driver != NULL ? driver : "(UNKNOWN)", + device != NULL ? device : "(UNKNOWN)"); + } + buffersize = shm->samples * (shm->samplebits / 8); + Con_Printf ("SDL audio driver: %s, %d bytes buffer\n", drivername, buffersize); + + shm->buffer = (unsigned char *) calloc (1, buffersize); + if (!shm->buffer) + { + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + shm = NULL; + Con_Printf ("Failed allocating memory for SDL audio\n"); + return false; + } + + SDL_PauseAudio(0); + + return true; +} + +int SNDDMA_GetDMAPos (void) +{ + return shm->samplepos; +} + +void SNDDMA_Shutdown (void) +{ + if (shm) + { + Con_Printf ("Shutting down SDL sound\n"); + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + if (shm->buffer) + free (shm->buffer); + shm->buffer = NULL; + shm = NULL; + } +} + +void SNDDMA_LockBuffer (void) +{ + SDL_LockAudio (); +} + +void SNDDMA_Submit (void) +{ + SDL_UnlockAudio(); +} + +void SNDDMA_BlockSound (void) +{ + SDL_PauseAudio(1); +} + +void SNDDMA_UnblockSound (void) +{ + SDL_PauseAudio(0); +} + diff --git a/source/snd_umx.c b/source/snd_umx.c new file mode 100644 index 0000000..6cda61f --- /dev/null +++ b/source/snd_umx.c @@ -0,0 +1,407 @@ +/* + * Unreal UMX container support. + * UPKG parsing partially based on Unreal Media Ripper (UMR) v0.3 + * by Andy Ward , with additional updates + * by O. Sezer - see git repo at https://github.com/sezero/umr/ + * + * The cheaper way, i.e. linear search of music object like libxmp + * and libmodplug does, is possible. With this however we're using + * the embedded offset, size and object type directly from the umx + * file, and I feel safer with it. + * + * Copyright (C) 2013 O. Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_UMX) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_umx.h" + +typedef int32_t fci_t; /* FCompactIndex */ + +#define UPKG_HDR_TAG 0x9e2a83c1 + +struct _genhist { /* for upkg versions >= 68 */ + int32_t export_count; + int32_t name_count; +}; + +struct upkg_hdr { + uint32_t tag; /* UPKG_HDR_TAG */ + int32_t file_version; + uint32_t pkg_flags; + int32_t name_count; /* number of names in name table (>= 0) */ + int32_t name_offset; /* offset to name table (>= 0) */ + int32_t export_count; /* num. exports in export table (>= 0) */ + int32_t export_offset; /* offset to export table (>= 0) */ + int32_t import_count; /* num. imports in export table (>= 0) */ + int32_t import_offset; /* offset to import table (>= 0) */ + + /* number of GUIDs in heritage table (>= 1) and table's offset: + * only with versions < 68. */ + int32_t heritage_count; + int32_t heritage_offset; + /* with versions >= 68: a GUID, a dword for generation count + * and export_count and name_count dwords for each generation: */ + uint32_t guid[4]; + int32_t generation_count; +#define UPKG_HDR_SIZE 64 /* 64 bytes up until here */ + /*struct _genhist *gen;*/ +}; +/*COMPILE_TIME_ASSERT(upkg_hdr, offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE);*/ +COMPILE_TIME_ASSERT(upkg_hdr, sizeof(struct upkg_hdr) == UPKG_HDR_SIZE); + +#define UMUSIC_IT 0 +#define UMUSIC_S3M 1 +#define UMUSIC_XM 2 +#define UMUSIC_MOD 3 +#define UMUSIC_WAV 4 +#define UMUSIC_MP2 5 + +static const char *mustype[] = { + "IT", "S3M", "XM", "MOD", + "WAV", "MP2", NULL +}; + +/* decode an FCompactIndex. + * original documentation by Tim Sweeney was at + * http://unreal.epicgames.com/Packages.htm + * also see Unreal Wiki: + * http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details + */ +static fci_t get_fci (const char *in, int *pos) +{ + int32_t a; + int size; + + size = 1; + a = in[0] & 0x3f; + + if (in[0] & 0x40) { + size++; + a |= (in[1] & 0x7f) << 6; + + if (in[1] & 0x80) { + size++; + a |= (in[2] & 0x7f) << 13; + + if (in[2] & 0x80) { + size++; + a |= (in[3] & 0x7f) << 20; + + if (in[3] & 0x80) { + size++; + a |= (in[4] & 0x3f) << 27; + } + } + } + } + + if (in[0] & 0x80) + a = -a; + + *pos += size; + + return a; +} + +static int get_objtype (fshandle_t *f, int32_t ofs, int type) +{ + char sig[16]; +_retry: + FS_fseek(f, ofs, SEEK_SET); + FS_fread(sig, 16, 1, f); + if (type == UMUSIC_IT) { + if (memcmp(sig, "IMPM", 4) == 0) + return UMUSIC_IT; + return -1; + } + if (type == UMUSIC_XM) { + if (memcmp(sig, "Extended Module:", 16) != 0) + return -1; + FS_fread(sig, 16, 1, f); + if (sig[0] != ' ') return -1; + FS_fread(sig, 16, 1, f); + if (sig[5] != 0x1a) return -1; + return UMUSIC_XM; + } + if (type == UMUSIC_MP2) { + unsigned char *p = (unsigned char *)sig; + uint16_t u = ((p[0] << 8) | p[1]) & 0xFFFE; + if (u == 0xFFFC || u == 0xFFF4) + return UMUSIC_MP2; + return -1; + } + if (type == UMUSIC_WAV) { + if (memcmp(sig, "RIFF", 4) == 0 && memcmp(&sig[8], "WAVE", 4) == 0) + return UMUSIC_WAV; + return -1; + } + + FS_fseek(f, ofs + 44, SEEK_SET); + FS_fread(sig, 4, 1, f); + if (type == UMUSIC_S3M) { + if (memcmp(sig, "SCRM", 4) == 0) + return UMUSIC_S3M; + /*return -1;*/ + /* SpaceMarines.umx and Starseek.umx from Return to NaPali + * report as "s3m" whereas the actual music format is "it" */ + type = UMUSIC_IT; + goto _retry; + } + + FS_fseek(f, ofs + 1080, SEEK_SET); + FS_fread(sig, 4, 1, f); + if (type == UMUSIC_MOD) { + if (memcmp(sig, "M.K.", 4) == 0 || memcmp(sig, "M!K!", 4) == 0) + return UMUSIC_MOD; + return -1; + } + + return -1; +} + +static int read_export (fshandle_t *f, const struct upkg_hdr *hdr, + int32_t *ofs, int32_t *objsize) +{ + char buf[40]; + int idx = 0, t; + + FS_fseek(f, *ofs, SEEK_SET); + if (FS_fread(buf, 4, 10, f) < 10) + return -1; + + if (hdr->file_version < 40) idx += 8; /* 00 00 00 00 00 00 00 00 */ + if (hdr->file_version < 60) idx += 16; /* 81 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 */ + get_fci(&buf[idx], &idx); /* skip junk */ + t = get_fci(&buf[idx], &idx); /* type_name */ + if (hdr->file_version > 61) idx += 4; /* skip export size */ + *objsize = get_fci(&buf[idx], &idx); + *ofs += idx; /* offset for real data */ + + return t; /* return type_name index */ +} + +static int read_typname(fshandle_t *f, const struct upkg_hdr *hdr, + int idx, char *out) +{ + int i, s; + long l; + char buf[64]; + + if (idx >= hdr->name_count) return -1; + buf[63] = '\0'; + for (i = 0, l = 0; i <= idx; i++) { + FS_fseek(f, hdr->name_offset + l, SEEK_SET); + FS_fread(buf, 1, 63, f); + if (hdr->file_version >= 64) { + s = *(signed char *)buf; /* numchars *including* terminator */ + if (s <= 0 || s > 64) return -1; + l += s + 5; /* 1 for buf[0], 4 for int32_t name_flags */ + } else { + l += (long)strlen(buf); + l += 5; /* 1 for terminator, 4 for int32_t name_flags */ + } + } + + strcpy(out, (hdr->file_version >= 64)? &buf[1] : buf); + return 0; +} + +static int probe_umx (fshandle_t *f, const struct upkg_hdr *hdr, + int32_t *ofs, int32_t *objsize) +{ + int i, idx, t; + int32_t s, pos; + long fsiz; + char buf[64]; + + idx = 0; + fsiz = FS_filelength (f); + + /* Find the offset and size of the first IT, S3M or XM + * by parsing the exports table. The umx files should + * have only one export. Kran32.umx from Unreal has two, + * but both pointing to the same music. */ + if (hdr->export_offset >= fsiz) return -1; + memset(buf, 0, 64); + FS_fseek(f, hdr->export_offset, SEEK_SET); + FS_fread(buf, 1, 64, f); + + get_fci(&buf[idx], &idx); /* skip class_index */ + get_fci(&buf[idx], &idx); /* skip super_index */ + if (hdr->file_version >= 60) idx += 4; /* skip int32 package_index */ + get_fci(&buf[idx], &idx); /* skip object_name */ + idx += 4; /* skip int32 object_flags */ + + s = get_fci(&buf[idx], &idx); /* get serial_size */ + if (s <= 0) return -1; + pos = get_fci(&buf[idx],&idx); /* get serial_offset */ + if (pos < 0 || pos > fsiz - 40) return -1; + + if ((t = read_export(f, hdr, &pos, &s)) < 0) return -1; + if (s <= 0 || s > fsiz - pos) return -1; + + if (read_typname(f, hdr, t, buf) < 0) return -1; + for (i = 0; mustype[i] != NULL; i++) { + if (!q_strcasecmp(buf, mustype[i])) { + t = i; + break; + } + } + if (mustype[i] == NULL) return -1; + if ((t = get_objtype(f, pos, t)) < 0) return -1; + + *ofs = pos; + *objsize = s; + return t; +} + +static int32_t probe_header (void *header) +{ + struct upkg_hdr *hdr; + unsigned char *p; + uint32_t *swp; + int i; + + /* byte swap the header - all members are 32 bit LE values */ + p = (unsigned char *) header; + swp = (uint32_t *) header; + for (i = 0; i < UPKG_HDR_SIZE/4; i++, p += 4) { + swp[i] = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + } + + hdr = (struct upkg_hdr *) header; + if (hdr->tag != UPKG_HDR_TAG) { + Con_DPrintf("Unknown header tag 0x%x\n", hdr->tag); + return -1; + } + if (hdr->name_count < 0 || + hdr->name_offset < 0 || + hdr->export_count < 0 || + hdr->export_offset < 0 || + hdr->import_count < 0 || + hdr->import_offset < 0 ) { + Con_DPrintf("Negative values in header\n"); + return -1; + } + + switch (hdr->file_version) { + case 35: case 37: /* Unreal beta - */ + case 40: case 41: /* 1998 */ + case 61:/* Unreal */ + case 62:/* Unreal Tournament */ + case 63:/* Return to NaPali */ + case 64:/* Unreal Tournament */ + case 66:/* Unreal Tournament */ + case 68:/* Unreal Tournament */ + case 69:/* Tactical Ops */ + case 75:/* Harry Potter and the Philosopher's Stone */ + case 76: /* mpeg layer II data */ + case 83:/* Mobile Forces */ + return 0; + } + + Con_DPrintf("Unknown upkg version %d\n", hdr->file_version); + return -1; +} + +static int process_upkg (fshandle_t *f, int32_t *ofs, int32_t *objsize) +{ + char header[UPKG_HDR_SIZE]; + + if (FS_fread(header, 1, UPKG_HDR_SIZE, f) < UPKG_HDR_SIZE) + return -1; + if (probe_header(header) < 0) + return -1; + + return probe_umx(f, (struct upkg_hdr *)header, ofs, objsize); +} + +static qboolean S_UMX_CodecInitialize (void) +{ + return true; +} + +static void S_UMX_CodecShutdown (void) +{ +} + +static qboolean S_UMX_CodecOpenStream (snd_stream_t *stream) +{ + int type; + int32_t ofs = 0, size = 0; + + type = process_upkg(&stream->fh, &ofs, &size); + if (type < 0) { + Con_DPrintf("%s: unrecognized umx\n", stream->name); + return false; + } + + Con_DPrintf("%s: %s data @ 0x%x, %d bytes\n", stream->name, mustype[type], ofs, size); + /* hack the fshandle_t start pos and length members so + * that only the relevant data is accessed from now on */ + stream->fh.start += ofs; + stream->fh.length = size; + FS_fseek(&stream->fh, 0, SEEK_SET); + + switch (type) { + case UMUSIC_IT: + case UMUSIC_S3M: + case UMUSIC_XM: + case UMUSIC_MOD: return S_CodecForwardStream(stream, CODECTYPE_MOD); + case UMUSIC_WAV: return S_CodecForwardStream(stream, CODECTYPE_WAV); + case UMUSIC_MP2: return S_CodecForwardStream(stream, CODECTYPE_MP3); + } + + return false; +} + +static int S_UMX_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return -1; +} + +static void S_UMX_CodecCloseStream (snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +static int S_UMX_CodecRewindStream (snd_stream_t *stream) +{ + return -1; +} + +snd_codec_t umx_codec = +{ + CODECTYPE_UMX, + true, /* always available. */ + "umx", + S_UMX_CodecInitialize, + S_UMX_CodecShutdown, + S_UMX_CodecOpenStream, + S_UMX_CodecReadStream, + S_UMX_CodecRewindStream, + S_UMX_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_UMX */ + diff --git a/source/snd_umx.h b/source/snd_umx.h new file mode 100644 index 0000000..da3107d --- /dev/null +++ b/source/snd_umx.h @@ -0,0 +1,12 @@ +/* Unreal UMX format support */ +#if !defined(_SND_UMX_H_) +#define _SND_UMX_H_ + +#if defined(USE_CODEC_UMX) + +extern snd_codec_t umx_codec; + +#endif /* USE_CODEC_UMX */ + +#endif /* ! _SND_UMX_H_ */ + diff --git a/source/snd_vorbis.c b/source/snd_vorbis.c new file mode 100644 index 0000000..16985dc --- /dev/null +++ b/source/snd_vorbis.c @@ -0,0 +1,204 @@ +/* + * Ogg/Vorbis streaming music support, loosely based on several open source + * Quake engine based projects with many modifications. + * + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_VORBIS) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_vorbis.h" + +#define OV_EXCLUDE_STATIC_CALLBACKS +#if defined(VORBIS_USE_TREMOR) +/* for Tremor / Vorbisfile api differences, + * see doc/diff.html in the Tremor package. */ +#include +#else +#include +#endif + +/* Vorbis codec can return the samples in a number of different + * formats, we use the standard signed short format. */ +#define VORBIS_SAMPLEBITS 16 +#define VORBIS_SAMPLEWIDTH 2 +#define VORBIS_SIGNED_DATA 1 + +/* CALLBACK FUNCTIONS: */ + +static int ovc_fclose (void *f) +{ + return 0; /* we fclose() elsewhere. */ +} + +static int ovc_fseek (void *f, ogg_int64_t off, int whence) +{ + if (f == NULL) return (-1); + return FS_fseek((fshandle_t *)f, (long) off, whence); +} + +static ov_callbacks ovc_qfs = +{ + (size_t (*)(void *, size_t, size_t, void *)) FS_fread, + (int (*)(void *, ogg_int64_t, int)) ovc_fseek, + (int (*)(void *)) ovc_fclose, + (long (*)(void *)) FS_ftell +}; + +static qboolean S_VORBIS_CodecInitialize (void) +{ + return true; +} + +static void S_VORBIS_CodecShutdown (void) +{ +} + +static qboolean S_VORBIS_CodecOpenStream (snd_stream_t *stream) +{ + OggVorbis_File *ovFile; + vorbis_info *ovf_info; + long numstreams; + int res; + + ovFile = (OggVorbis_File *) Z_Malloc(sizeof(OggVorbis_File)); + stream->priv = ovFile; + res = ov_open_callbacks(&stream->fh, ovFile, NULL, 0, ovc_qfs); + if (res != 0) + { + Con_Printf("%s is not a valid Ogg Vorbis file (error %i).\n", + stream->name, res); + goto _fail; + } + + if (!ov_seekable(ovFile)) + { + Con_Printf("Stream %s not seekable.\n", stream->name); + goto _fail; + } + + ovf_info = ov_info(ovFile, 0); + if (!ovf_info) + { + Con_Printf("Unable to get stream info for %s.\n", stream->name); + goto _fail; + } + + /* FIXME: handle section changes */ + numstreams = ov_streams(ovFile); + if (numstreams != 1) + { + Con_Printf("More than one (%ld) stream in %s.\n", + numstreams, stream->name); + goto _fail; + } + + if (ovf_info->channels != 1 && ovf_info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + ovf_info->channels, stream->name); + goto _fail; + } + + stream->info.rate = ovf_info->rate; + stream->info.channels = ovf_info->channels; + stream->info.bits = VORBIS_SAMPLEBITS; + stream->info.width = VORBIS_SAMPLEWIDTH; + + return true; +_fail: + if (res == 0) + ov_clear(ovFile); + Z_Free(ovFile); + return false; +} + +static int S_VORBIS_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int section; /* FIXME: handle section changes */ + int cnt, res, rem; + char * ptr; + + cnt = 0; rem = bytes; + ptr = (char *) buffer; + while (1) + { + /* # ov_read() from libvorbisfile returns the decoded PCM audio + * in requested endianness, signedness and word size. + * # ov_read() from Tremor (libvorbisidec) returns decoded audio + * always in host-endian, signed 16 bit PCM format. + * # For both of the libraries, if the audio is multichannel, + * the channels are interleaved in the output buffer. + */ + res = ov_read( (OggVorbis_File *)stream->priv, ptr, rem, +#ifndef VORBIS_USE_TREMOR + host_bigendian, + VORBIS_SAMPLEWIDTH, + VORBIS_SIGNED_DATA, +#endif + §ion ); + if (res <= 0) + break; + rem -= res; + cnt += res; + if (rem <= 0) + break; + ptr += res; + } + + if (res < 0) + return res; + return cnt; +} + +static void S_VORBIS_CodecCloseStream (snd_stream_t *stream) +{ + ov_clear((OggVorbis_File *)stream->priv); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_VORBIS_CodecRewindStream (snd_stream_t *stream) +{ +/* for libvorbisfile, the ov_time_seek() position argument + * is seconds as doubles, whereas for Tremor libvorbisidec + * it is milliseconds as 64 bit integers. + */ + return ov_time_seek ((OggVorbis_File *)stream->priv, 0); +} + +snd_codec_t vorbis_codec = +{ + CODECTYPE_VORBIS, + true, /* always available. */ + "ogg", + S_VORBIS_CodecInitialize, + S_VORBIS_CodecShutdown, + S_VORBIS_CodecOpenStream, + S_VORBIS_CodecReadStream, + S_VORBIS_CodecRewindStream, + S_VORBIS_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_VORBIS */ + diff --git a/source/snd_vorbis.h b/source/snd_vorbis.h new file mode 100644 index 0000000..0f1cc9e --- /dev/null +++ b/source/snd_vorbis.h @@ -0,0 +1,13 @@ +/* Ogg/Vorbis streaming music support. */ + +#if !defined(_SND_VORBIS_H_) +#define _SND_VORBIS_H_ 1 + +#if defined(USE_CODEC_VORBIS) + +extern snd_codec_t vorbis_codec; + +#endif /* USE_CODEC_VORBIS */ + +#endif /* ! _SND_VORBIS_H_ */ + diff --git a/source/snd_wave.c b/source/snd_wave.c new file mode 100644 index 0000000..b31011a --- /dev/null +++ b/source/snd_wave.c @@ -0,0 +1,276 @@ +/* + * WAV streaming music support. Adapted from ioquake3 with changes. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_WAVE) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_wave.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong (FILE *f) +{ + int v; + + fread(&v, 1, sizeof(v), f); + + return LittleLong(v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort(FILE *f) +{ + short v; + + fread(&v, 1, sizeof(v), f); + + return LittleShort(v); +} + +/* +================= +WAV_ReadChunkInfo +================= +*/ +static int WAV_ReadChunkInfo(FILE *f, char *name) +{ + int len, r; + + name[4] = 0; + + r = fread(name, 1, 4, f); + if (r != 4) + return -1; + + len = FGetLittleLong(f); + if (len < 0) + { + Con_Printf("WAV: Negative chunk length\n"); + return -1; + } + + return len; +} + +/* +================= +WAV_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int WAV_FindRIFFChunk(FILE *f, const char *chunk) +{ + char name[5]; + int len; + + while ((len = WAV_ReadChunkInfo(f, name)) >= 0) + { + /* If this is the right chunk, return */ + if (!strncmp(name, chunk, 4)) + return len; + len = ((len + 1) & ~1); /* pad by 2 . */ + + /* Not the right chunk - skip it */ + fseek(f, len, SEEK_CUR); + } + + return -1; +} + +/* +================= +WAV_ReadRIFFHeader +================= +*/ +static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info) +{ + char dump[16]; + int wav_format; + int fmtlen = 0; + + if (fread(dump, 1, 12, file) < 12 || + strncmp(dump, "RIFF", 4) != 0 || + strncmp(&dump[8], "WAVE", 4) != 0) + { + Con_Printf("%s is missing RIFF/WAVE chunks\n", name); + return false; + } + + /* Scan for the format chunk */ + if ((fmtlen = WAV_FindRIFFChunk(file, "fmt ")) < 0) + { + Con_Printf("%s is missing fmt chunk\n", name); + return false; + } + + /* Save the parameters */ + wav_format = FGetLittleShort(file); + if (wav_format != WAV_FORMAT_PCM) + { + Con_Printf("%s is not Microsoft PCM format\n", name); + return false; + } + + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + info->bits = FGetLittleShort(file); + + if (info->bits != 8 && info->bits != 16) + { + Con_Printf("%s is not 8 or 16 bit\n", name); + return false; + } + + info->width = info->bits / 8; + info->dataofs = 0; + + /* Skip the rest of the format chunk if required */ + if (fmtlen > 16) + { + fmtlen -= 16; + fseek(file, fmtlen, SEEK_CUR); + } + + /* Scan for the data chunk */ + if ((info->size = WAV_FindRIFFChunk(file, "data")) < 0) + { + Con_Printf("%s is missing data chunk\n", name); + return false; + } + + if (info->channels != 1 && info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + info->channels, name); + return false; + } + info->samples = (info->size / info->width) / info->channels; + if (info->samples == 0) + { + Con_Printf("%s has zero samples\n", name); + return false; + } + + return true; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +static qboolean S_WAV_CodecOpenStream(snd_stream_t *stream) +{ + long start = stream->fh.start; + + /* Read the RIFF header */ + /* The file reads are sequential, therefore no need + * for the FS_*() functions: We will manipulate the + * file by ourselves from now on. */ + if (!WAV_ReadRIFFHeader(stream->name, stream->fh.file, &stream->info)) + return false; + + stream->fh.start = ftell(stream->fh.file); /* reset to data position */ + if (stream->fh.start - start + stream->info.size > stream->fh.length) + { + Con_Printf("%s data size mismatch\n", stream->name); + return false; + } + + return true; +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->fh.pos; + int i, samples; + + if (remaining <= 0) + return 0; + if (bytes > remaining) + bytes = remaining; + stream->fh.pos += bytes; + fread(buffer, 1, bytes, stream->fh.file); + if (stream->info.width == 2) + { + samples = bytes / 2; + for (i = 0; i < samples; i++) + ((short *)buffer)[i] = LittleShort( ((short *)buffer)[i] ); + } + return bytes; +} + +static void S_WAV_CodecCloseStream (snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +static int S_WAV_CodecRewindStream (snd_stream_t *stream) +{ + FS_rewind(&stream->fh); + return 0; +} + +static qboolean S_WAV_CodecInitialize (void) +{ + return true; +} + +static void S_WAV_CodecShutdown (void) +{ +} + +snd_codec_t wav_codec = +{ + CODECTYPE_WAVE, + true, /* always available. */ + "wav", + S_WAV_CodecInitialize, + S_WAV_CodecShutdown, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecRewindStream, + S_WAV_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_WAVE */ + diff --git a/source/snd_wave.h b/source/snd_wave.h new file mode 100644 index 0000000..7f7f50b --- /dev/null +++ b/source/snd_wave.h @@ -0,0 +1,13 @@ +/* WAV streaming music support. */ + +#if !defined(_SND_WAVE_H_) +#define _SND_WAVE_H_ + +#if defined(USE_CODEC_WAVE) + +extern snd_codec_t wav_codec; + +#endif /* USE_CODEC_WAVE */ + +#endif /* ! _SND_WAVE_H_ */ + diff --git a/source/snd_xmp.c b/source/snd_xmp.c new file mode 100644 index 0000000..27b55a5 --- /dev/null +++ b/source/snd_xmp.c @@ -0,0 +1,168 @@ +/* tracker music (module file) decoding support using libxmp >= v4.2.0 + * https://sourceforge.net/projects/xmp/ + * https://github.com/cmatsuoka/libxmp.git + * + * Copyright (C) 2016 O.Sezer + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_XMP) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_xmp.h" +#if defined(_WIN32) && defined(XMP_NO_DLL) +#define BUILDING_STATIC +#endif +#include +#if ((XMP_VERCODE+0) < 0x040200) +#error libxmp version 4.2 or newer is required +#endif + +static int S_XMP_StartPlay (snd_stream_t *stream) +{ + int fmt = 0; + + if (stream->info.channels == 1) + fmt |= XMP_FORMAT_MONO; + if (stream->info.width == 1) + fmt |= XMP_FORMAT_8BIT|XMP_FORMAT_UNSIGNED; + + return xmp_start_player((xmp_context)stream->priv, stream->info.rate, fmt); +} + +static qboolean S_XMP_CodecInitialize (void) +{ + return true; +} + +static void S_XMP_CodecShutdown (void) +{ +} + +static qboolean S_XMP_CodecOpenStream (snd_stream_t *stream) +{ +/* need to load the whole file into memory and pass it to libxmp + * using xmp_load_module_from_memory() which requires libxmp >= 4.2. + * libxmp-4.0/4.1 only have xmp_load_module() which accepts a file + * name which isn't good with files in containers like paks, etc. */ + xmp_context c; + byte *moddata; + long len; + int mark; + + c = xmp_create_context(); + if (c == NULL) + return false; + + len = FS_filelength (&stream->fh); + mark = Hunk_LowMark(); + moddata = (byte *) Hunk_Alloc(len); + FS_fread(moddata, 1, len, &stream->fh); + if (xmp_load_module_from_memory(c, moddata, len) != 0) + { + Con_DPrintf("Could not load module %s\n", stream->name); + goto err1; + } + + Hunk_FreeToLowMark(mark); /* free original file data */ + stream->priv = c; + if (shm->speed > XMP_MAX_SRATE) + stream->info.rate = 44100; + else if (shm->speed < XMP_MIN_SRATE) + stream->info.rate = 11025; + else stream->info.rate = shm->speed; + stream->info.bits = shm->samplebits; + stream->info.width = stream->info.bits / 8; + stream->info.channels = shm->channels; + + if (S_XMP_StartPlay(stream) != 0) + goto err2; + /* percentual left/right channel separation, default is 70. */ + if (stream->info.channels == 2) + if (xmp_set_player(c, XMP_PLAYER_MIX, 100) != 0) + goto err3; + /* interpolation type, default is XMP_INTERP_LINEAR */ + if (xmp_set_player(c, XMP_PLAYER_INTERP, XMP_INTERP_SPLINE) != 0) + goto err3; + + return true; + +err3: xmp_end_player(c); +err2: xmp_release_module(c); +err1: xmp_free_context(c); + return false; +} + +static int S_XMP_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int r; + /* xmp_play_buffer() requires libxmp >= 4.1. it will write + * native-endian pcm data to the buffer. if the data write + * is partial, the rest of the buffer will be zero-filled. + * the last param is the number that the current sequence of + * the song will be looped at max. */ + r = xmp_play_buffer((xmp_context)stream->priv, buffer, bytes, 1); + if (r == 0) { + return bytes; + } + if (r == -XMP_END) { + Con_DPrintf("XMP EOF\n"); + return 0; + } + return -1; +} + +static void S_XMP_CodecCloseStream (snd_stream_t *stream) +{ + xmp_context c = (xmp_context)stream->priv; + xmp_end_player(c); + xmp_release_module(c); + xmp_free_context(c); + S_CodecUtilClose(&stream); +} + +static int S_XMP_CodecRewindStream (snd_stream_t *stream) +{ + int ret; + + ret = S_XMP_StartPlay(stream); + if (ret < 0) return ret; + + /*ret = xmp_set_position((xmp_context)stream->priv, 0);*/ + ret = xmp_seek_time((xmp_context)stream->priv, 0); + if (ret < 0) return ret; + + return 0; +} + +snd_codec_t xmp_codec = +{ + CODECTYPE_MOD, + true, /* always available. */ + "s3m", + S_XMP_CodecInitialize, + S_XMP_CodecShutdown, + S_XMP_CodecOpenStream, + S_XMP_CodecReadStream, + S_XMP_CodecRewindStream, + S_XMP_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_XMP */ diff --git a/source/snd_xmp.h b/source/snd_xmp.h new file mode 100644 index 0000000..9516a49 --- /dev/null +++ b/source/snd_xmp.h @@ -0,0 +1,12 @@ +/* module tracker decoding support using libxmp */ +#if !defined(_SND_XMP_H_) +#define _SND_XMP_H_ + +#if defined(USE_CODEC_XMP) + +extern snd_codec_t xmp_codec; + +#endif /* USE_CODEC_XMP */ + +#endif /* ! _SND_XMP_H_ */ + diff --git a/source/spritegn.h b/source/spritegn.h new file mode 100644 index 0000000..1065052 --- /dev/null +++ b/source/spritegn.h @@ -0,0 +1,117 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +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 __SPRITEGEN_H +#define __SPRITEGEN_H + +// +// 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. * +// ********************************************************** + +//------------------------------------------------------- +// 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 +// +//------------------------------------------------------- + +#ifdef INCLUDELIBS + +#include +#include +#include +#include + +#include "cmdlib.h" +#include "scriplib.h" +#include "dictlib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "mathlib.h" + +#endif + +#define SPRITE_VERSION 1 + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum {ST_SYNC=0, ST_RAND } synctype_t; +#endif + +// TODO: shorten these? +typedef struct { + int ident; + int version; + int type; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dsprite_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 + +typedef struct { + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct { + int numframes; +} dspritegroup_t; + +typedef struct { + float interval; +} dspriteinterval_t; + +typedef enum { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t; + +typedef struct { + spriteframetype_t type; +} dspriteframetype_t; + +#define IDSPRITEHEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDSP" + +#endif /* __SPRITEGEN_H */ + diff --git a/source/stb_image_write.h b/source/stb_image_write.h new file mode 100644 index 0000000..d590443 --- /dev/null +++ b/source/stb_image_write.h @@ -0,0 +1,684 @@ +/* QuakeSpasm: kept only the jpg writer, the only thing we use, and removed all others. */ + +/* stb_image_write - v1.07 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + JPEG + Jon Olick (original jo_jpeg.cpp code) + Daniel Gibson + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +#if 0 /* not used in QuakeSpasm */ +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); +#endif + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + unsigned char head1[] = { 0xFF,0xC0,0,0x11,8, /*[5]*/0,/*[6]*/0,/*[7]*/0,/*[8]*/0, + 3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + head1[5] = (unsigned char)(height>>8); + head1[6] = STBIW_UCHAR(height); + head1[7] = (unsigned char)(width>>8); + head1[8] = STBIW_UCHAR(width); + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + const unsigned char *imageData = (const unsigned char *)data; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + int x, y, pos; + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float YDU[64], UDU[64], VDU[64]; + for(row = y, pos = 0; row < y+8; ++row) { + for(col = x; col < x+8; ++col, ++pos) { + int p = row*width*comp + col*comp; + float r, g, b; + if(row >= height) { + p -= width*comp*(row+1 - height); + } + if(col >= width) { + p -= comp*(col+1 - width); + } + + r = imageData[p+0]; + g = imageData[p+ofsG]; + b = imageData[p+ofsB]; + YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; + UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; + VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +#if 0 /* not used in QuakeSpasm */ +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} +#endif + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/strl_fn.h b/source/strl_fn.h new file mode 100644 index 0000000..af7fc14 --- /dev/null +++ b/source/strl_fn.h @@ -0,0 +1,11 @@ +/* header file for BSD strlcat and strlcpy */ + +#ifndef __STRLFUNCS_H +#define __STRLFUNCS_H + +/* use our own copies of strlcpy and strlcat taken from OpenBSD */ +extern size_t q_strlcpy (char *dst, const char *src, size_t size); +extern size_t q_strlcat (char *dst, const char *src, size_t size); + +#endif /* __STRLFUNCS_H */ + diff --git a/source/strlcat.c b/source/strlcat.c new file mode 100644 index 0000000..48b91f6 --- /dev/null +++ b/source/strlcat.c @@ -0,0 +1,59 @@ +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * 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. + */ + +#include +#include + +#include "strl_fn.h" + +/* + * 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 +q_strlcat (char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + 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 */ +} + diff --git a/source/strlcpy.c b/source/strlcpy.c new file mode 100644 index 0000000..754e70d --- /dev/null +++ b/source/strlcpy.c @@ -0,0 +1,55 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * 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. + */ + +#include +#include + +#include "strl_fn.h" + +/* + * 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 +q_strlcpy (char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* 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 */ +} + diff --git a/source/sv_main.c b/source/sv_main.c new file mode 100644 index 0000000..91deeac --- /dev/null +++ b/source/sv_main.c @@ -0,0 +1,1484 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +server_t sv; +server_static_t svs; + +static char localmodels[MAX_MODELS][8]; // inline model names for precache + +int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz + +extern qboolean pr_alpha_supported; //johnfitz + +//============================================================================ + +/* +=============== +SV_Protocol_f +=============== +*/ +void SV_Protocol_f (void) +{ + int i; + + switch (Cmd_Argc()) + { + case 1: + Con_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol); + break; + case 2: + i = atoi(Cmd_Argv(1)); + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) + Con_Printf ("sv_protocol must be %i or %i or %i\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + else + { + sv_protocol = i; + if (sv.active) + Con_Printf ("changes will not take effect until the next level load.\n"); + } + break; + default: + Con_SafePrintf ("usage: sv_protocol \n"); + break; + } +} + +/* +=============== +SV_Init +=============== +*/ +void SV_Init (void) +{ + int i; + const char *p; + extern cvar_t sv_maxvelocity; + extern cvar_t sv_gravity; + extern cvar_t sv_nostep; + extern cvar_t sv_freezenonclients; + extern cvar_t sv_friction; + extern cvar_t sv_edgefriction; + extern cvar_t sv_stopspeed; + extern cvar_t sv_maxspeed; + extern cvar_t sv_accelerate; + extern cvar_t sv_idealpitchscale; + extern cvar_t sv_aim; + extern cvar_t sv_altnoclip; //johnfitz + + sv.edicts = NULL; // ericw -- sv.edicts switched to use malloc() + + Cvar_RegisterVariable (&sv_maxvelocity); + Cvar_RegisterVariable (&sv_gravity); + Cvar_RegisterVariable (&sv_friction); + Cvar_SetCallback (&sv_gravity, Host_Callback_Notify); + Cvar_SetCallback (&sv_friction, Host_Callback_Notify); + Cvar_RegisterVariable (&sv_edgefriction); + Cvar_RegisterVariable (&sv_stopspeed); + Cvar_RegisterVariable (&sv_maxspeed); + Cvar_SetCallback (&sv_maxspeed, Host_Callback_Notify); + Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_idealpitchscale); + Cvar_RegisterVariable (&sv_aim); + Cvar_RegisterVariable (&sv_nostep); + Cvar_RegisterVariable (&sv_freezenonclients); + Cvar_RegisterVariable (&sv_altnoclip); //johnfitz + + Cmd_AddCommand ("sv_protocol", &SV_Protocol_f); //johnfitz + + for (i=0 ; i MAX_DATAGRAM-16) + return; + MSG_WriteByte (&sv.datagram, svc_particle); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocolflags); + for (i=0 ; i<3 ; i++) + { + v = dir[i]*16; + if (v > 127) + v = 127; + else if (v < -128) + v = -128; + MSG_WriteChar (&sv.datagram, v); + } + MSG_WriteByte (&sv.datagram, count); + MSG_WriteByte (&sv.datagram, color); +} + +/* +================== +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 +allready 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 (edict_t *entity, int channel, const char *sample, int volume, float attenuation) +{ + int sound_num, ent; + int i, field_mask; + + if (volume < 0 || volume > 255) + Host_Error ("SV_StartSound: volume = %i", volume); + + if (attenuation < 0 || attenuation > 4) + Host_Error ("SV_StartSound: attenuation = %f", attenuation); + + if (channel < 0 || channel > 7) + Host_Error ("SV_StartSound: channel = %i", channel); + + if (sv.datagram.cursize > MAX_DATAGRAM-16) + return; + +// find precache number for sound + for (sound_num = 1; sound_num < MAX_SOUNDS && sv.sound_precache[sound_num]; sound_num++) + { + if (!strcmp(sample, sv.sound_precache[sound_num])) + break; + } + + if (sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num]) + { + Con_Printf ("SV_StartSound: %s not precacheed\n", sample); + return; + } + + ent = 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; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (ent >= 8192) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + else + field_mask |= SND_LARGEENTITY; + } + if (sound_num >= 256 || channel >= 8) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + else + field_mask |= SND_LARGESOUND; + } + //johnfitz + +// directed messages go only to the entity the 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, attenuation*64); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (field_mask & SND_LARGEENTITY) + { + MSG_WriteShort (&sv.datagram, ent); + MSG_WriteByte (&sv.datagram, channel); + } + else + MSG_WriteShort (&sv.datagram, (ent<<3) | channel); + if (field_mask & SND_LARGESOUND) + MSG_WriteShort (&sv.datagram, sound_num); + else + MSG_WriteByte (&sv.datagram, sound_num); + //johnfitz + + for (i = 0; i < 3; i++) + MSG_WriteCoord (&sv.datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); +} + +/* +============================================================================== + +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) +{ + const char **s; + char message[2048]; + int i; //johnfitz + + MSG_WriteByte (&client->message, svc_print); + sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version + MSG_WriteString (&client->message,message); + + MSG_WriteByte (&client->message, svc_serverinfo); + MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION + + if (sv.protocol == PROTOCOL_RMQ) + { + // mh - now send protocol flags so that the client knows the protocol features to expect + MSG_WriteLong (&client->message, sv.protocolflags); + } + + MSG_WriteByte (&client->message, svs.maxclients); + + if (!coop.value && deathmatch.value) + MSG_WriteByte (&client->message, GAME_DEATHMATCH); + else + MSG_WriteByte (&client->message, GAME_COOP); + + MSG_WriteString (&client->message, PR_GetString(sv.edicts->v.message)); + + //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 + for (i=0,s = sv.model_precache+1 ; *s; s++,i++) + if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) + MSG_WriteString (&client->message, *s); + MSG_WriteByte (&client->message, 0); + + for (i=0,s = sv.sound_precache+1 ; *s ; s++,i++) + if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) + MSG_WriteString (&client->message, *s); + MSG_WriteByte (&client->message, 0); + //johnfitz + +// send music + MSG_WriteByte (&client->message, svc_cdtrack); + MSG_WriteByte (&client->message, sv.edicts->v.sounds); + MSG_WriteByte (&client->message, sv.edicts->v.sounds); + +// set view + MSG_WriteByte (&client->message, svc_setview); + MSG_WriteShort (&client->message, NUM_FOR_EDICT(client->edict)); + + MSG_WriteByte (&client->message, svc_signonnum); + MSG_WriteByte (&client->message, 1); + + client->sendsignon = true; + client->spawned = false; // need prespawn, spawn, etc +} + +/* +================ +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) +{ + edict_t *ent; + client_t *client; + int edictnum; + struct qsocket_s *netconnection; + int i; + float spawn_parms[NUM_SPAWN_PARMS]; + + client = svs.clients + clientnum; + + Con_DPrintf ("Client %s connected\n", NET_QSocketGetAddressString(client->netconnection)); + + edictnum = clientnum+1; + + ent = EDICT_NUM(edictnum); + +// set up the client_t + netconnection = client->netconnection; + + if (sv.loadgame) + memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); + memset (client, 0, sizeof(*client)); + client->netconnection = netconnection; + + strcpy (client->name, "unconnected"); + client->active = true; + client->spawned = false; + client->edict = ent; + client->message.data = client->msgbuf; + client->message.maxsize = sizeof(client->msgbuf); + client->message.allowoverflow = true; // we can catch it + + if (sv.loadgame) + memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms)); + else + { + // call the progs to get default spawn parms for the new client + PR_ExecuteProgram (pr_global_struct->SetNewParms); + for (i=0 ; ispawn_parms[i] = (&pr_global_struct->parm1)[i]; + } + + SV_SendServerinfo (client); +} + + +/* +=================== +SV_CheckForNewClients + +=================== +*/ +void SV_CheckForNewClients (void) +{ + struct qsocket_s *ret; + int i; + +// +// check for new connections +// + while (1) + { + ret = NET_CheckNewConnections (); + if (!ret) + break; + + // + // init a new client structure + // + for (i=0 ; icontents < 0) + { + if (node->contents != CONTENTS_SOLID) + { + pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter + for (i=0 ; iplane; + d = DotProduct (org, plane->normal) - plane->dist; + if (d > 8) + node = node->children[0]; + else if (d < -8) + node = node->children[1]; + else + { // go down both + SV_AddToFatPVS (org, node->children[0], worldmodel); //johnfitz -- worldmodel as a parameter + node = node->children[1]; + } + } +} + +/* +============= +SV_FatPVS + +Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the +given point. +============= +*/ +byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter +{ + fatbytes = (worldmodel->numleafs+7)>>3; // ericw -- was +31, assumed to be a bug/typo + if (fatpvs == NULL || fatbytes > fatpvs_capacity) + { + fatpvs_capacity = fatbytes; + fatpvs = (byte *) realloc (fatpvs, fatpvs_capacity); + if (!fatpvs) + Sys_Error ("SV_FatPVS: realloc() failed on %d bytes", fatpvs_capacity); + } + + Q_memset (fatpvs, 0, fatbytes); + SV_AddToFatPVS (org, worldmodel->nodes, worldmodel); //johnfitz -- worldmodel as a parameter + return fatpvs; +} + +/* +============= +SV_VisibleToClient -- johnfitz + +PVS test encapsulated in a nice function +============= +*/ +qboolean SV_VisibleToClient (edict_t *client, edict_t *test, qmodel_t *worldmodel) +{ + byte *pvs; + vec3_t org; + int i; + + VectorAdd (client->v.origin, client->v.view_ofs, org); + pvs = SV_FatPVS (org, worldmodel); + + for (i=0 ; i < test->num_leafs ; i++) + if (pvs[test->leafnums[i] >> 3] & (1 << (test->leafnums[i]&7) )) + return true; + + return false; +} + +//============================================================================= + +/* +============= +SV_WriteEntitiesToClient + +============= +*/ +void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) +{ + int e, i; + int bits; + byte *pvs; + vec3_t org; + float miss; + edict_t *ent; + +// find the client's PVS + VectorAdd (clent->v.origin, clent->v.view_ofs, org); + pvs = SV_FatPVS (org, sv.worldmodel); + +// send over all entities (excpet the client) that touch the pvs + ent = NEXT_EDICT(sv.edicts); + for (e=1 ; ev.modelindex || !PR_GetString(ent->v.model)[0]) + continue; + + //johnfitz -- don't send model>255 entities if protocol is 15 + if (sv.protocol == PROTOCOL_NETQUAKE && (int)ent->v.modelindex & 0xFF00) + continue; + + // ignore if not touching a PV leaf + for (i=0 ; i < ent->num_leafs ; i++) + if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) )) + break; + + // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. + // + // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs + // for us to say whether it's in the PVS, so don't try to vis cull it. + // this commonly happens with rotators, because they often have huge bboxes + // spanning the entire map, or really tall lifts, etc. + if (i == ent->num_leafs && ent->num_leafs < MAX_ENT_LEAFS) + continue; // not visible + } + + //johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally + //assumed here. And, for protocol 85 the max size is actually 24 bytes. + if (msg->cursize + 24 > msg->maxsize) + { + //johnfitz -- less spammy overflow message + if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime ) + { + Con_Printf ("Packet overflow!\n"); + dev_overflows.packetsize = realtime; + } + goto stats; + //johnfitz + } + +// send an update + bits = 0; + + for (i=0 ; i<3 ; i++) + { + miss = ent->v.origin[i] - ent->baseline.origin[i]; + if ( miss < -0.1 || miss > 0.1 ) + bits |= U_ORIGIN1<v.angles[0] != ent->baseline.angles[0] ) + bits |= U_ANGLE1; + + if ( ent->v.angles[1] != ent->baseline.angles[1] ) + bits |= U_ANGLE2; + + if ( ent->v.angles[2] != ent->baseline.angles[2] ) + bits |= U_ANGLE3; + + if (ent->v.movetype == MOVETYPE_STEP) + bits |= U_STEP; // don't mess up the step animation + + if (ent->baseline.colormap != ent->v.colormap) + bits |= U_COLORMAP; + + if (ent->baseline.skin != ent->v.skin) + bits |= U_SKIN; + + if (ent->baseline.frame != ent->v.frame) + bits |= U_FRAME; + + if (ent->baseline.effects != ent->v.effects) + bits |= U_EFFECTS; + + if (ent->baseline.modelindex != ent->v.modelindex) + bits |= U_MODEL; + + //johnfitz -- alpha + if (pr_alpha_supported) + { + // TODO: find a cleaner place to put this code + eval_t *val; + val = GetEdictFieldValue(ent, "alpha"); + if (val) + ent->alpha = ENTALPHA_ENCODE(val->_float); + } + + //don't send invisible entities unless they have effects + if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) + continue; + //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol != PROTOCOL_NETQUAKE) + { + + if (ent->baseline.alpha != ent->alpha) bits |= U_ALPHA; + if (bits & U_FRAME && (int)ent->v.frame & 0xFF00) bits |= U_FRAME2; + if (bits & U_MODEL && (int)ent->v.modelindex & 0xFF00) bits |= U_MODEL2; + if (ent->sendinterval) bits |= U_LERPFINISH; + if (bits >= 65536) bits |= U_EXTEND1; + if (bits >= 16777216) bits |= U_EXTEND2; + } + //johnfitz + + if (e >= 256) + bits |= U_LONGENTITY; + + if (bits >= 256) + bits |= U_MOREBITS; + + // + // write the message + // + MSG_WriteByte (msg, bits | U_SIGNAL); + + if (bits & U_MOREBITS) + MSG_WriteByte (msg, bits>>8); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_EXTEND1) + MSG_WriteByte(msg, bits>>16); + if (bits & U_EXTEND2) + MSG_WriteByte(msg, bits>>24); + //johnfitz + + if (bits & U_LONGENTITY) + MSG_WriteShort (msg,e); + else + MSG_WriteByte (msg,e); + + if (bits & U_MODEL) + MSG_WriteByte (msg, ent->v.modelindex); + if (bits & U_FRAME) + MSG_WriteByte (msg, ent->v.frame); + if (bits & U_COLORMAP) + MSG_WriteByte (msg, ent->v.colormap); + if (bits & U_SKIN) + MSG_WriteByte (msg, ent->v.skin); + if (bits & U_EFFECTS) + MSG_WriteByte (msg, ent->v.effects); + if (bits & U_ORIGIN1) + MSG_WriteCoord (msg, ent->v.origin[0], sv.protocolflags); + if (bits & U_ANGLE1) + MSG_WriteAngle(msg, ent->v.angles[0], sv.protocolflags); + if (bits & U_ORIGIN2) + MSG_WriteCoord (msg, ent->v.origin[1], sv.protocolflags); + if (bits & U_ANGLE2) + MSG_WriteAngle(msg, ent->v.angles[1], sv.protocolflags); + if (bits & U_ORIGIN3) + MSG_WriteCoord (msg, ent->v.origin[2], sv.protocolflags); + if (bits & U_ANGLE3) + MSG_WriteAngle(msg, ent->v.angles[2], sv.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_ALPHA) + MSG_WriteByte(msg, ent->alpha); + if (bits & U_FRAME2) + MSG_WriteByte(msg, (int)ent->v.frame >> 8); + if (bits & U_MODEL2) + MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); + if (bits & U_LERPFINISH) + MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); + //johnfitz + } + + //johnfitz -- devstats +stats: + if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) + Con_DWarning ("%i byte packet exceeds standard limit of 1024 (max = %d).\n", msg->cursize, msg->maxsize); + dev_stats.packetsize = msg->cursize; + dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); + //johnfitz +} + +/* +============= +SV_CleanupEnts + +============= +*/ +void SV_CleanupEnts (void) +{ + int e; + edict_t *ent; + + ent = NEXT_EDICT(sv.edicts); + for (e=1 ; ev.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH; + } +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) +{ + int bits; + int i; + edict_t *other; + int items; + eval_t *val; + +// +// send a damage message +// + if (ent->v.dmg_take || ent->v.dmg_save) + { + other = PROG_TO_EDICT(ent->v.dmg_inflictor); + MSG_WriteByte (msg, svc_damage); + MSG_WriteByte (msg, ent->v.dmg_save); + MSG_WriteByte (msg, ent->v.dmg_take); + for (i=0 ; i<3 ; i++) + MSG_WriteCoord (msg, other->v.origin[i] + 0.5*(other->v.mins[i] + other->v.maxs[i]), sv.protocolflags ); + + ent->v.dmg_take = 0; + ent->v.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 ( ent->v.fixangle ) + { + MSG_WriteByte (msg, svc_setangle); + for (i=0 ; i < 3 ; i++) + MSG_WriteAngle (msg, ent->v.angles[i], sv.protocolflags ); + ent->v.fixangle = 0; + } + + bits = 0; + + if (ent->v.view_ofs[2] != DEFAULT_VIEWHEIGHT) + bits |= SU_VIEWHEIGHT; + + if (ent->v.idealpitch) + bits |= SU_IDEALPITCH; + +// stuff the sigil bits into the high bits of items for sbar, or else +// mix in items2 + val = GetEdictFieldValue(ent, "items2"); + + if (val) + items = (int)ent->v.items | ((int)val->_float << 23); + else + items = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28); + + bits |= SU_ITEMS; + + if ( (int)ent->v.flags & FL_ONGROUND) + bits |= SU_ONGROUND; + + if ( ent->v.waterlevel >= 2) + bits |= SU_INWATER; + + for (i=0 ; i<3 ; i++) + { + if (ent->v.punchangle[i]) + bits |= (SU_PUNCH1<v.velocity[i]) + bits |= (SU_VELOCITY1<v.weaponframe) + bits |= SU_WEAPONFRAME; + + if (ent->v.armorvalue) + bits |= SU_ARMOR; + +// if (ent->v.weapon) + bits |= SU_WEAPON; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol != PROTOCOL_NETQUAKE) + { + if (bits & SU_WEAPON && SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) & 0xFF00) bits |= SU_WEAPON2; + if ((int)ent->v.armorvalue & 0xFF00) bits |= SU_ARMOR2; + if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2; + if ((int)ent->v.ammo_shells & 0xFF00) bits |= SU_SHELLS2; + if ((int)ent->v.ammo_nails & 0xFF00) bits |= SU_NAILS2; + if ((int)ent->v.ammo_rockets & 0xFF00) bits |= SU_ROCKETS2; + if ((int)ent->v.ammo_cells & 0xFF00) bits |= SU_CELLS2; + if (bits & SU_WEAPONFRAME && (int)ent->v.weaponframe & 0xFF00) bits |= SU_WEAPONFRAME2; + if (bits & SU_WEAPON && ent->alpha != ENTALPHA_DEFAULT) bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha + if (bits >= 65536) bits |= SU_EXTEND1; + if (bits >= 16777216) bits |= SU_EXTEND2; + } + //johnfitz + +// send the data + + MSG_WriteByte (msg, svc_clientdata); + MSG_WriteShort (msg, bits); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16); + if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24); + //johnfitz + + if (bits & SU_VIEWHEIGHT) + MSG_WriteChar (msg, ent->v.view_ofs[2]); + + if (bits & SU_IDEALPITCH) + MSG_WriteChar (msg, ent->v.idealpitch); + + for (i=0 ; i<3 ; i++) + { + if (bits & (SU_PUNCH1<v.punchangle[i]); + if (bits & (SU_VELOCITY1<v.velocity[i]/16); + } + +// [always sent] if (bits & SU_ITEMS) + MSG_WriteLong (msg, items); + + if (bits & SU_WEAPONFRAME) + MSG_WriteByte (msg, ent->v.weaponframe); + if (bits & SU_ARMOR) + MSG_WriteByte (msg, ent->v.armorvalue); + if (bits & SU_WEAPON) + MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel))); + + MSG_WriteShort (msg, ent->v.health); + MSG_WriteByte (msg, ent->v.currentammo); + MSG_WriteByte (msg, ent->v.ammo_shells); + MSG_WriteByte (msg, ent->v.ammo_nails); + MSG_WriteByte (msg, ent->v.ammo_rockets); + MSG_WriteByte (msg, ent->v.ammo_cells); + + if (standard_quake) + { + MSG_WriteByte (msg, ent->v.weapon); + } + else + { + for(i=0;i<32;i++) + { + if ( ((int)ent->v.weapon) & (1<v.weaponmodel)) >> 8); + if (bits & SU_ARMOR2) + MSG_WriteByte (msg, (int)ent->v.armorvalue >> 8); + if (bits & SU_AMMO2) + MSG_WriteByte (msg, (int)ent->v.currentammo >> 8); + if (bits & SU_SHELLS2) + MSG_WriteByte (msg, (int)ent->v.ammo_shells >> 8); + if (bits & SU_NAILS2) + MSG_WriteByte (msg, (int)ent->v.ammo_nails >> 8); + if (bits & SU_ROCKETS2) + MSG_WriteByte (msg, (int)ent->v.ammo_rockets >> 8); + if (bits & SU_CELLS2) + MSG_WriteByte (msg, (int)ent->v.ammo_cells >> 8); + if (bits & SU_WEAPONFRAME2) + MSG_WriteByte (msg, (int)ent->v.weaponframe >> 8); + if (bits & SU_WEAPONALPHA) + MSG_WriteByte (msg, ent->alpha); //for now, weaponalpha = client entity alpha + //johnfitz +} + +/* +======================= +SV_SendClientDatagram +======================= +*/ +qboolean SV_SendClientDatagram (client_t *client) +{ + byte buf[MAX_DATAGRAM]; + sizebuf_t msg; + + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + + //johnfitz -- if client is nonlocal, use smaller max size so packets aren't fragmented + if (Q_strcmp(NET_QSocketGetAddressString(client->netconnection), "LOCAL") != 0) + msg.maxsize = DATAGRAM_MTU; + //johnfitz + + MSG_WriteByte (&msg, svc_time); + MSG_WriteFloat (&msg, sv.time); + +// add the client specific data to the datagram + SV_WriteClientdataToMessage (client->edict, &msg); + + SV_WriteEntitiesToClient (client->edict, &msg); + +// copy the server datagram if there is space + if (msg.cursize + sv.datagram.cursize < msg.maxsize) + SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); + +// send the datagram + if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) + { + SV_DropClient (true);// if the message couldn't send, kick off + return false; + } + + return true; +} + +/* +======================= +SV_UpdateToReliableMessages +======================= +*/ +void SV_UpdateToReliableMessages (void) +{ + int i, j; + client_t *client; + +// check for changes to be sent over the reliable streams + for (i=0, host_client = svs.clients ; iold_frags != host_client->edict->v.frags) + { + for (j=0, client = svs.clients ; jactive) + continue; + MSG_WriteByte (&client->message, svc_updatefrags); + MSG_WriteByte (&client->message, i); + MSG_WriteShort (&client->message, host_client->edict->v.frags); + } + + host_client->old_frags = host_client->edict->v.frags; + } + } + + for (j=0, client = svs.clients ; jactive) + continue; + SZ_Write (&client->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); + } + + SZ_Clear (&sv.reliable_datagram); +} + + +/* +======================= +SV_SendNop + +Send a nop message without trashing or sending the accumulated client +message buffer +======================= +*/ +void SV_SendNop (client_t *client) +{ + sizebuf_t msg; + byte buf[4]; + + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + + MSG_WriteChar (&msg, svc_nop); + + if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) + SV_DropClient (true); // if the message couldn't send, kick off + client->last_message = realtime; +} + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages (void) +{ + int i; + +// update frags, names, etc + SV_UpdateToReliableMessages (); + +// build individual updates + for (i=0, host_client = svs.clients ; iactive) + continue; + + if (host_client->spawned) + { + if (!SV_SendClientDatagram (host_client)) + continue; + } + else + { + // the player isn't totally in the game yet + // send small keepalive messages if too much time has passed + // send a full message when the next signon stage has been requested + // some other message data (name changes, etc) may accumulate + // between signon stages + if (!host_client->sendsignon) + { + if (realtime - host_client->last_message > 5) + SV_SendNop (host_client); + continue; // don't send out non-signon messages + } + } + + // check for an overflowed message. Should only happen + // on a very fucked up connection that backs up a lot, then + // changes level + if (host_client->message.overflowed) + { + SV_DropClient (true); + host_client->message.overflowed = false; + continue; + } + + if (host_client->message.cursize || host_client->dropasap) + { + if (!NET_CanSendMessage (host_client->netconnection)) + { +// I_Printf ("can't write\n"); + continue; + } + + if (host_client->dropasap) + SV_DropClient (false); // went to another level + else + { + if (NET_SendMessage (host_client->netconnection + , &host_client->message) == -1) + SV_DropClient (true); // if the message couldn't send, kick off + SZ_Clear (&host_client->message); + host_client->last_message = realtime; + host_client->sendsignon = false; + } + } + } + + +// clear muzzle flashes + SV_CleanupEnts (); +} + + +/* +============================================================================== + +SERVER SPAWNING + +============================================================================== +*/ + +/* +================ +SV_ModelIndex + +================ +*/ +int SV_ModelIndex (const char *name) +{ + int i; + + if (!name || !name[0]) + return 0; + + for (i=0 ; ifree) + continue; + if (entnum > svs.maxclients && !svent->v.modelindex) + continue; + + // + // create entity baseline + // + VectorCopy (svent->v.origin, svent->baseline.origin); + VectorCopy (svent->v.angles, svent->baseline.angles); + svent->baseline.frame = svent->v.frame; + svent->baseline.skin = svent->v.skin; + if (entnum > 0 && entnum <= svs.maxclients) + { + svent->baseline.colormap = entnum; + svent->baseline.modelindex = SV_ModelIndex("progs/player.mdl"); + svent->baseline.alpha = ENTALPHA_DEFAULT; //johnfitz -- alpha support + } + else + { + svent->baseline.colormap = 0; + svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); + svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support + } + + //johnfitz -- PROTOCOL_FITZQUAKE + bits = 0; + if (sv.protocol == PROTOCOL_NETQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values + { + if (svent->baseline.modelindex & 0xFF00) + svent->baseline.modelindex = 0; + if (svent->baseline.frame & 0xFF00) + svent->baseline.frame = 0; + svent->baseline.alpha = ENTALPHA_DEFAULT; + } + else //decide which extra data needs to be sent + { + if (svent->baseline.modelindex & 0xFF00) + bits |= B_LARGEMODEL; + if (svent->baseline.frame & 0xFF00) + bits |= B_LARGEFRAME; + if (svent->baseline.alpha != ENTALPHA_DEFAULT) + bits |= B_ALPHA; + } + //johnfitz + + // + // add to the message + // + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits) + MSG_WriteByte (&sv.signon, svc_spawnbaseline2); + else + MSG_WriteByte (&sv.signon, svc_spawnbaseline); + //johnfitz + + MSG_WriteShort (&sv.signon,entnum); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits) + MSG_WriteByte (&sv.signon, bits); + + if (bits & B_LARGEMODEL) + MSG_WriteShort (&sv.signon, svent->baseline.modelindex); + else + MSG_WriteByte (&sv.signon, svent->baseline.modelindex); + + if (bits & B_LARGEFRAME) + MSG_WriteShort (&sv.signon, svent->baseline.frame); + else + MSG_WriteByte (&sv.signon, svent->baseline.frame); + //johnfitz + + MSG_WriteByte (&sv.signon, svent->baseline.colormap); + MSG_WriteByte (&sv.signon, svent->baseline.skin); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(&sv.signon, svent->baseline.origin[i], sv.protocolflags); + MSG_WriteAngle(&sv.signon, svent->baseline.angles[i], sv.protocolflags); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & B_ALPHA) + MSG_WriteByte (&sv.signon, svent->baseline.alpha); + //johnfitz + } +} + + +/* +================ +SV_SendReconnect + +Tell all the clients that the server is changing levels +================ +*/ +void SV_SendReconnect (void) +{ + byte data[128]; + sizebuf_t msg; + + msg.data = data; + msg.cursize = 0; + msg.maxsize = sizeof(data); + + MSG_WriteChar (&msg, svc_stufftext); + MSG_WriteString (&msg, "reconnect\n"); + NET_SendToAll (&msg, 5.0); + + if (!isDedicated) + Cmd_ExecuteString ("reconnect\n", src_command); +} + + +/* +================ +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 = pr_global_struct->serverflags; + + for (i=0, host_client = svs.clients ; iactive) + continue; + + // call the progs to get default spawn parms for the new client + pr_global_struct->self = EDICT_TO_PROG(host_client->edict); + PR_ExecuteProgram (pr_global_struct->SetChangeParms); + for (j=0 ; jspawn_parms[j] = (&pr_global_struct->parm1)[j]; + } +} + + +/* +================ +SV_SpawnServer + +This is called at the start of each level +================ +*/ +extern float scr_centertime_off; +void SV_SpawnServer (const char *server) +{ + static char dummy[8] = { 0,0,0,0,0,0,0,0 }; + edict_t *ent; + int i; + + // let's not have any servers with no name + if (hostname.string[0] == 0) + Cvar_Set ("hostname", "UNNAMED"); + scr_centertime_off = 0; + + Con_DPrintf ("SpawnServer: %s\n",server); + svs.changelevel_issued = false; // now safe to issue another + +// +// tell all connected clients that we are going to a new level +// + if (sv.active) + { + SV_SendReconnect (); + } + +// +// make cvars consistant +// + if (coop.value) + Cvar_Set ("deathmatch", "0"); + current_skill = (int)(skill.value + 0.5); + if (current_skill < 0) + current_skill = 0; + if (current_skill > 3) + current_skill = 3; + + Cvar_SetValue ("skill", (float)current_skill); + +// +// set up the new server +// + //memset (&sv, 0, sizeof(sv)); + Host_ClearMemory (); + + q_strlcpy (sv.name, server, sizeof(sv.name)); + + sv.protocol = sv_protocol; // johnfitz + + if (sv.protocol == PROTOCOL_RMQ) + { + // set up the protocol flags used by this server + // (note - these could be cvar-ised so that server admins could choose the protocol features used by their servers) + sv.protocolflags = PRFL_INT32COORD | PRFL_SHORTANGLE; + } + else sv.protocolflags = 0; + +// load progs to get entity field count + PR_LoadProgs (); + +// allocate server memory + /* Host_ClearMemory() called above already cleared the whole sv structure */ + sv.max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar + sv.edicts = (edict_t *) malloc (sv.max_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + + 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 + sv.num_edicts = svs.maxclients+1; + memset(sv.edicts, 0, sv.num_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + for (i=0 ; inumsubmodels ; i++) + { + sv.model_precache[1+i] = localmodels[i]; + sv.models[i+1] = Mod_ForName (localmodels[i], false); + } + +// +// load the rest of the entities +// + ent = EDICT_NUM(0); + memset (&ent->v, 0, progs->entityfields * 4); + ent->free = false; + ent->v.model = PR_SetEngineString(sv.worldmodel->name); + ent->v.modelindex = 1; // world model + ent->v.solid = SOLID_BSP; + ent->v.movetype = MOVETYPE_PUSH; + + if (coop.value) + pr_global_struct->coop = coop.value; + else + pr_global_struct->deathmatch = deathmatch.value; + + pr_global_struct->mapname = PR_SetEngineString(sv.name); + +// serverflags are for cross level information (sigils) + pr_global_struct->serverflags = svs.serverflags; + + ED_LoadFromFile (sv.worldmodel->entities); + + sv.active = true; + +// all setup is completed, any further precache statements are errors + sv.state = ss_active; + +// run two frames to allow everything to settle + host_frametime = 0.1; + SV_Physics (); + SV_Physics (); + +// create a baseline for more efficient communications + SV_CreateBaseline (); + + //johnfitz -- warn if signon buffer larger than standard server can handle + if (sv.signon.cursize > 8000-2) //max size that will fit into 8000-sized client->message buffer with 2 extra bytes on the end + Con_DWarning ("%i byte signon buffer exceeds standard limit of 7998 (max = %d).\n", sv.signon.cursize, sv.signon.maxsize); + //johnfitz + +// send serverinfo to all connected clients + for (i=0,host_client = svs.clients ; iactive) + SV_SendServerinfo (host_client); + + Con_DPrintf ("Server spawned.\n"); +} + diff --git a/source/sv_move.c b/source/sv_move.c new file mode 100644 index 0000000..fd19084 --- /dev/null +++ b/source/sv_move.c @@ -0,0 +1,421 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +#define STEPSIZE 18 + +/* +============= +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 (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->v.origin, ent->v.mins, mins); + VectorAdd (ent->v.origin, ent->v.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_PointContents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, 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_Move (start, vec3_origin, vec3_origin, stop, true, ent); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + edict_t *enemy; + +// try the move + VectorCopy (ent->v.origin, oldorg); + VectorAdd (ent->v.origin, move, neworg); + +// flying monsters don't step up + if ( (int)ent->v.flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->v.origin, move, neworg); + enemy = PROG_TO_EDICT(ent->v.enemy); + if (i == 0 && enemy != sv.edicts) + { + dz = ent->v.origin[2] - PROG_TO_EDICT(ent->v.enemy)->v.origin[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, neworg, false, ent); + + if (trace.fraction == 1) + { + if ( ((int)ent->v.flags & FL_SWIM) && SV_PointContents(trace.endpos) == CONTENTS_EMPTY ) + return false; // swim monster left water + + VectorCopy (trace.endpos, ent->v.origin); + if (relink) + SV_LinkEdict (ent, true); + return true; + } + + if (enemy == sv.edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += STEPSIZE; + VectorCopy (neworg, end); + end[2] -= STEPSIZE*2; + + trace = SV_Move (neworg, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= STEPSIZE; + trace = SV_Move (neworg, ent->v.mins, ent->v.maxs, end, false, ent); + if (trace.allsolid || trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->v.origin, move, ent->v.origin); + if (relink) + SV_LinkEdict (ent, true); + ent->v.flags = (int)ent->v.flags & ~FL_ONGROUND; +// Con_Printf ("fall down\n"); + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->v.origin); + + if (!SV_CheckBottom (ent)) + { + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + SV_LinkEdict (ent, true); + return true; + } + VectorCopy (oldorg, ent->v.origin); + return false; + } + + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { +// Con_Printf ("back on ground\n"); + ent->v.flags = (int)ent->v.flags & ~FL_PARTIALGROUND; + } + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + SV_LinkEdict (ent, true); + return true; +} + + +//============================================================================ + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +void PF_changeyaw (void); +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->v.ideal_yaw = yaw; + PF_changeyaw(); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->v.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->v.angles[YAW] - ent->v.ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->v.origin); + } + SV_LinkEdict (ent, true); + return true; + } + SV_LinkEdict (ent, true); + + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ +// Con_Printf ("SV_FixCheckBottom\n"); + + ent->v.flags = (int)ent->v.flags | FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + olddir = anglemod( (int)(actor->v.ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->v.origin[0] - actor->v.origin[0]; + deltay = enemy->v.origin[1] - actor->v.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs((int)deltay)>abs((int)deltax)) // ericw -- explicit int cast to suppress clang suggestion to use fabsf + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->v.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 (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->v.absmin[i] > ent->v.absmax[i] + dist) + return false; + if (goal->v.absmax[i] < ent->v.absmin[i] - dist) + return false; + } + return true; +} + +/* +====================== +SV_MoveToGoal + +====================== +*/ +void SV_MoveToGoal (void) +{ + edict_t *ent, *goal; + float dist; + + ent = PROG_TO_EDICT(pr_global_struct->self); + goal = PROG_TO_EDICT(ent->v.goalentity); + dist = G_FLOAT(OFS_PARM0); + + if ( !( (int)ent->v.flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + G_FLOAT(OFS_RETURN) = 0; + return; + } + +// if the next step hits the enemy, return immediately + if ( PROG_TO_EDICT(ent->v.enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || + !SV_StepDirection (ent, ent->v.ideal_yaw, dist)) + { + SV_NewChaseDir (ent, goal, dist); + } +} + diff --git a/source/sv_phys.c b/source/sv_phys.c new file mode 100644 index 0000000..eb5019a --- /dev/null +++ b/source/sv_phys.c @@ -0,0 +1,1232 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ + +cvar_t sv_friction = {"sv_friction","4",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_stopspeed = {"sv_stopspeed","100",CVAR_NONE}; +cvar_t sv_gravity = {"sv_gravity","800",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_maxvelocity = {"sv_maxvelocity","2000",CVAR_NONE}; +cvar_t sv_nostep = {"sv_nostep","0",CVAR_NONE}; +cvar_t sv_freezenonclients = {"sv_freezenonclients","0",CVAR_NONE}; + + +#define MOVE_EPSILON 0.01 + +void SV_Physics_Toss (edict_t *ent); + +/* +================ +SV_CheckAllEnts +================ +*/ +void SV_CheckAllEnts (void) +{ + int e; + edict_t *check; + +// see if any solid entities are inside the final position + check = NEXT_EDICT(sv.edicts); + for (e=1 ; efree) + continue; + if (check->v.movetype == MOVETYPE_PUSH + || check->v.movetype == MOVETYPE_NONE + || check->v.movetype == MOVETYPE_NOCLIP) + continue; + + if (SV_TestEntityPosition (check)) + Con_Printf ("entity in invalid position\n"); + } +} + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (IS_NAN(ent->v.velocity[i])) + { + Con_Printf ("Got a NaN velocity on %s\n", PR_GetString(ent->v.classname)); + ent->v.velocity[i] = 0; + } + if (IS_NAN(ent->v.origin[i])) + { + Con_Printf ("Got a NaN origin on %s\n", PR_GetString(ent->v.classname)); + ent->v.origin[i] = 0; + } + if (ent->v.velocity[i] > sv_maxvelocity.value) + ent->v.velocity[i] = sv_maxvelocity.value; + else if (ent->v.velocity[i] < -sv_maxvelocity.value) + ent->v.velocity[i] = -sv_maxvelocity.value; + } +} + +/* +============= +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 (edict_t *ent) +{ + float thinktime; + float oldframe; //johnfitz + int i; //johnfitz + + thinktime = ent->v.nextthink; + if (thinktime <= 0 || thinktime > sv.time + host_frametime) + return true; + + if (thinktime < sv.time) + thinktime = sv.time; // don't let things stay in the past. + // it is possible to start that way + // by a trigger with a local time. + + oldframe = ent->v.frame; //johnfitz + + ent->v.nextthink = 0; + pr_global_struct->time = thinktime; + pr_global_struct->self = EDICT_TO_PROG(ent); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + PR_ExecuteProgram (ent->v.think); + +//johnfitz -- PROTOCOL_FITZQUAKE +//capture interval to nextthink here and send it to client for better +//lerp timing, but only if interval is not 0.1 (which client assumes) + ent->sendinterval = false; + if (!ent->free && ent->v.nextthink && (ent->v.movetype == MOVETYPE_STEP || ent->v.frame != oldframe)) + { + i = Q_rint((ent->v.nextthink-thinktime)*255); + if (i >= 0 && i < 256 && i != 25 && i != 26) //25 and 26 are close enough to 0.1 to not send + ent->sendinterval = true; + } +//johnfitz + + return !ent->free; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, edict_t *e2) +{ + int old_self, old_other; + + old_self = pr_global_struct->self; + old_other = pr_global_struct->other; + + pr_global_struct->time = sv.time; + if (e1->v.touch && e1->v.solid != SOLID_NOT) + { + pr_global_struct->self = EDICT_TO_PROG(e1); + pr_global_struct->other = EDICT_TO_PROG(e2); + PR_ExecuteProgram (e1->v.touch); + } + + if (e2->v.touch && e2->v.solid != SOLID_NOT) + { + pr_global_struct->self = EDICT_TO_PROG(e2); + pr_global_struct->other = EDICT_TO_PROG(e1); + PR_ExecuteProgram (e2->v.touch); + } + + pr_global_struct->self = old_self; + pr_global_struct->other = old_other; +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +If steptrace is not NULL, the trace of any vertical wall hit will be stored +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, trace_t *steptrace) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->v.velocity, original_velocity); + VectorCopy (ent->v.velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + for (bumpcount=0 ; bumpcountv.velocity[0] && !ent->v.velocity[1] && !ent->v.velocity[2]) + break; + + for (i=0 ; i<3 ; i++) + end[i] = ent->v.origin[i] + time_left * ent->v.velocity[i]; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->v.velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->v.origin); + VectorCopy (ent->v.velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + if (!trace.ent) + Sys_Error ("SV_FlyMove: !trace.ent"); + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if (trace.ent->v.solid == SOLID_BSP) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + if (steptrace) + *steptrace = trace; // save for player extrafriction + } + +// +// run the impact function +// + SV_Impact (ent, trace.ent); + if (ent->free) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->v.velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; iv.velocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->v.velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->v.velocity); + VectorScale (dir, d, ent->v.velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->v.velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->v.velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + float ent_gravity; + eval_t *val; + + val = GetEdictFieldValue(ent, "gravity"); + if (val && val->_float) + ent_gravity = val->_float; + else + ent_gravity = 1.0; + + ent->v.velocity[2] -= ent_gravity * sv_gravity.value * host_frametime; +} + + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t end; + + VectorAdd (ent->v.origin, push, end); + + if (ent->v.movetype == MOVETYPE_FLYMISSILE) + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_MISSILE, ent); + else if (ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT) + // only clip against bmodels + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NOMONSTERS, ent); + else + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent); + + VectorCopy (trace.endpos, ent->v.origin); + SV_LinkEdict (ent, true); + + if (trace.ent) + SV_Impact (ent, trace.ent); + + return trace; +} + + +/* +============ +SV_PushMove +============ +*/ +void SV_PushMove (edict_t *pusher, float movetime) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs, move; + vec3_t entorig, pushorig; + int num_moved; + edict_t **moved_edict; //johnfitz -- dynamically allocate + vec3_t *moved_from; //johnfitz -- dynamically allocate + int mark; //johnfitz + + if (!pusher->v.velocity[0] && !pusher->v.velocity[1] && !pusher->v.velocity[2]) + { + pusher->v.ltime += movetime; + return; + } + + for (i=0 ; i<3 ; i++) + { + move[i] = pusher->v.velocity[i] * movetime; + mins[i] = pusher->v.absmin[i] + move[i]; + maxs[i] = pusher->v.absmax[i] + move[i]; + } + + VectorCopy (pusher->v.origin, pushorig); + +// move the pusher to it's final position + + VectorAdd (pusher->v.origin, move, pusher->v.origin); + pusher->v.ltime += movetime; + SV_LinkEdict (pusher, false); + + //johnfitz -- dynamically allocate + mark = Hunk_LowMark (); + moved_edict = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); + moved_from = (vec3_t *) Hunk_Alloc (sv.num_edicts*sizeof(vec3_t)); + //johnfitz + +// see if any solid entities are inside the final position + num_moved = 0; + check = NEXT_EDICT(sv.edicts); + for (e=1 ; efree) + continue; + if (check->v.movetype == MOVETYPE_PUSH + || check->v.movetype == MOVETYPE_NONE + || check->v.movetype == MOVETYPE_NOCLIP) + continue; + + // if the entity is standing on the pusher, it will definately be moved + if ( ! ( ((int)check->v.flags & FL_ONGROUND) + && PROG_TO_EDICT(check->v.groundentity) == pusher) ) + { + if ( check->v.absmin[0] >= maxs[0] + || check->v.absmin[1] >= maxs[1] + || check->v.absmin[2] >= maxs[2] + || check->v.absmax[0] <= mins[0] + || check->v.absmax[1] <= mins[1] + || check->v.absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + // remove the onground flag for non-players + if (check->v.movetype != MOVETYPE_WALK) + check->v.flags = (int)check->v.flags & ~FL_ONGROUND; + + VectorCopy (check->v.origin, entorig); + VectorCopy (check->v.origin, moved_from[num_moved]); + moved_edict[num_moved] = check; + num_moved++; + + // try moving the contacted entity + pusher->v.solid = SOLID_NOT; + SV_PushEntity (check, move); + pusher->v.solid = SOLID_BSP; + + // if it is still inside the pusher, block + block = SV_TestEntityPosition (check); + if (block) + { // fail the move + if (check->v.mins[0] == check->v.maxs[0]) + continue; + if (check->v.solid == SOLID_NOT || check->v.solid == SOLID_TRIGGER) + { // corpse + check->v.mins[0] = check->v.mins[1] = 0; + VectorCopy (check->v.mins, check->v.maxs); + continue; + } + + VectorCopy (entorig, check->v.origin); + SV_LinkEdict (check, true); + + VectorCopy (pushorig, pusher->v.origin); + SV_LinkEdict (pusher, false); + pusher->v.ltime -= movetime; + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (pusher->v.blocked) + { + pr_global_struct->self = EDICT_TO_PROG(pusher); + pr_global_struct->other = EDICT_TO_PROG(check); + PR_ExecuteProgram (pusher->v.blocked); + } + + // move back any entities we already moved + for (i=0 ; iv.origin); + SV_LinkEdict (moved_edict[i], false); + } + Hunk_FreeToLowMark (mark); //johnfitz + return; + } + } + + Hunk_FreeToLowMark (mark); //johnfitz + +} + +/* +================ +SV_Physics_Pusher + +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + float thinktime; + float oldltime; + float movetime; + + oldltime = ent->v.ltime; + + thinktime = ent->v.nextthink; + if (thinktime < ent->v.ltime + host_frametime) + { + movetime = thinktime - ent->v.ltime; + if (movetime < 0) + movetime = 0; + } + else + movetime = host_frametime; + + if (movetime) + { + SV_PushMove (ent, movetime); // advances ent->v.ltime if not blocked + } + + if (thinktime > oldltime && thinktime <= ent->v.ltime) + { + ent->v.nextthink = 0; + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + PR_ExecuteProgram (ent->v.think); + if (ent->free) + return; + } + +} + + +/* +=============================================================================== + +CLIENT MOVEMENT + +=============================================================================== +*/ + +/* +============= +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 (edict_t *ent) +{ + int i, j; + int z; + vec3_t org; + + if (!SV_TestEntityPosition(ent)) + { + VectorCopy (ent->v.origin, ent->v.oldorigin); + return; + } + + VectorCopy (ent->v.origin, org); + VectorCopy (ent->v.oldorigin, ent->v.origin); + if (!SV_TestEntityPosition(ent)) + { + Con_DPrintf ("Unstuck.\n"); + SV_LinkEdict (ent, true); + return; + } + + for (z=0 ; z< 18 ; z++) + for (i=-1 ; i <= 1 ; i++) + for (j=-1 ; j <= 1 ; j++) + { + ent->v.origin[0] = org[0] + i; + ent->v.origin[1] = org[1] + j; + ent->v.origin[2] = org[2] + z; + if (!SV_TestEntityPosition(ent)) + { + Con_DPrintf ("Unstuck.\n"); + SV_LinkEdict (ent, true); + return; + } + } + + VectorCopy (org, ent->v.origin); + Con_DPrintf ("player is stuck.\n"); +} + + +/* +============= +SV_CheckWater +============= +*/ +qboolean SV_CheckWater (edict_t *ent) +{ + vec3_t point; + int cont; + + point[0] = ent->v.origin[0]; + point[1] = ent->v.origin[1]; + point[2] = ent->v.origin[2] + ent->v.mins[2] + 1; + + ent->v.waterlevel = 0; + ent->v.watertype = CONTENTS_EMPTY; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + { + ent->v.watertype = cont; + ent->v.waterlevel = 1; + point[2] = ent->v.origin[2] + (ent->v.mins[2] + ent->v.maxs[2])*0.5; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + { + ent->v.waterlevel = 2; + point[2] = ent->v.origin[2] + ent->v.view_ofs[2]; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + ent->v.waterlevel = 3; + } + } + + return ent->v.waterlevel > 1; +} + +/* +============ +SV_WallFriction + +============ +*/ +void SV_WallFriction (edict_t *ent, trace_t *trace) +{ + vec3_t forward, right, up; + float d, i; + vec3_t into, side; + + AngleVectors (ent->v.v_angle, forward, right, up); + d = DotProduct (trace->plane.normal, forward); + + d += 0.5; + if (d >= 0) + return; + +// cut the tangential velocity + i = DotProduct (trace->plane.normal, ent->v.velocity); + VectorScale (trace->plane.normal, i, into); + VectorSubtract (ent->v.velocity, into, side); + + ent->v.velocity[0] = side[0] * (1 + d); + ent->v.velocity[1] = side[1] * (1 + d); +} + +/* +===================== +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 (edict_t *ent, vec3_t oldvel) +{ + int i; + vec3_t oldorg; + vec3_t dir; + int clip; + trace_t steptrace; + + VectorCopy (ent->v.origin, oldorg); + VectorCopy (vec3_origin, 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 (ent, dir); + +// retry the original move + ent->v.velocity[0] = oldvel[0]; + ent->v. velocity[1] = oldvel[1]; + ent->v. velocity[2] = 0; + clip = SV_FlyMove (ent, 0.1, &steptrace); + + if ( fabs(oldorg[1] - ent->v.origin[1]) > 4 + || fabs(oldorg[0] - ent->v.origin[0]) > 4 ) + { +//Con_DPrintf ("unstuck!\n"); + return clip; + } + +// go back to the original pos and try again + VectorCopy (oldorg, ent->v.origin); + } + + VectorCopy (vec3_origin, ent->v.velocity); + return 7; // still not moving +} + +/* +===================== +SV_WalkMove + +Only used by players +====================== +*/ +#define STEPSIZE 18 +void SV_WalkMove (edict_t *ent) +{ + vec3_t upmove, downmove; + vec3_t oldorg, oldvel; + vec3_t nosteporg, nostepvel; + int clip; + int oldonground; + trace_t steptrace, downtrace; + +// +// do a regular slide move unless it looks like you ran into a step +// + oldonground = (int)ent->v.flags & FL_ONGROUND; + ent->v.flags = (int)ent->v.flags & ~FL_ONGROUND; + + VectorCopy (ent->v.origin, oldorg); + VectorCopy (ent->v.velocity, oldvel); + + clip = SV_FlyMove (ent, host_frametime, &steptrace); + + if ( !(clip & 2) ) + return; // move didn't block on a step + + if (!oldonground && ent->v.waterlevel == 0) + return; // don't stair up while jumping + + if (ent->v.movetype != MOVETYPE_WALK) + return; // gibbed by a trigger + + if (sv_nostep.value) + return; + + if ( (int)sv_player->v.flags & FL_WATERJUMP ) + return; + + VectorCopy (ent->v.origin, nosteporg); + VectorCopy (ent->v.velocity, nostepvel); + +// +// try moving up and forward to go up a step +// + VectorCopy (oldorg, ent->v.origin); // back to start pos + + VectorCopy (vec3_origin, upmove); + VectorCopy (vec3_origin, downmove); + upmove[2] = STEPSIZE; + downmove[2] = -STEPSIZE + oldvel[2]*host_frametime; + +// move up + SV_PushEntity (ent, upmove); // FIXME: don't link? + +// move forward + ent->v.velocity[0] = oldvel[0]; + ent->v. velocity[1] = oldvel[1]; + ent->v. velocity[2] = 0; + clip = SV_FlyMove (ent, host_frametime, &steptrace); + +// check for stuckness, possibly due to the limited precision of floats +// in the clipping hulls + if (clip) + { + if ( fabs(oldorg[1] - ent->v.origin[1]) < 0.03125 + && fabs(oldorg[0] - ent->v.origin[0]) < 0.03125 ) + { // stepping up didn't make any progress + clip = SV_TryUnstick (ent, oldvel); + } + } + +// extra friction based on view angle + if ( clip & 2 ) + SV_WallFriction (ent, &steptrace); + +// move down + downtrace = SV_PushEntity (ent, downmove); // FIXME: don't link? + + if (downtrace.plane.normal[2] > 0.7) + { + if (ent->v.solid == SOLID_BSP) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(downtrace.ent); + } + } + else + { +// 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 (nosteporg, ent->v.origin); + VectorCopy (nostepvel, ent->v.velocity); + } +} + + +/* +================ +SV_Physics_Client + +Player character actions +================ +*/ +void SV_Physics_Client (edict_t *ent, int num) +{ + if ( ! svs.clients[num-1].active ) + return; // unconnected slot + +// +// call standard client pre-think +// + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (pr_global_struct->PlayerPreThink); + +// +// do a move +// + SV_CheckVelocity (ent); + +// +// decide which move function to call +// + switch ((int)ent->v.movetype) + { + case MOVETYPE_NONE: + if (!SV_RunThink (ent)) + return; + break; + + case MOVETYPE_WALK: + if (!SV_RunThink (ent)) + return; + if (!SV_CheckWater (ent) && ! ((int)ent->v.flags & FL_WATERJUMP) ) + SV_AddGravity (ent); + SV_CheckStuck (ent); + SV_WalkMove (ent); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + SV_Physics_Toss (ent); + break; + + case MOVETYPE_FLY: + if (!SV_RunThink (ent)) + return; + SV_FlyMove (ent, host_frametime, NULL); + break; + + case MOVETYPE_NOCLIP: + if (!SV_RunThink (ent)) + return; + VectorMA (ent->v.origin, host_frametime, ent->v.velocity, ent->v.origin); + break; + + default: + Sys_Error ("SV_Physics_client: bad movetype %i", (int)ent->v.movetype); + } + +// +// call standard player post-think +// + SV_LinkEdict (ent, true); + + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (pr_global_struct->PlayerPostThink); +} + +//============================================================================ + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->v.angles, host_frametime, ent->v.avelocity, ent->v.angles); + VectorMA (ent->v.origin, host_frametime, ent->v.velocity, ent->v.origin); + + SV_LinkEdict (ent, false); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_CheckWaterTransition + +============= +*/ +void SV_CheckWaterTransition (edict_t *ent) +{ + int cont; + + cont = SV_PointContents (ent->v.origin); + + if (!ent->v.watertype) + { // just spawned here + ent->v.watertype = cont; + ent->v.waterlevel = 1; + return; + } + + if (cont <= CONTENTS_WATER) + { + if (ent->v.watertype == CONTENTS_EMPTY) + { // just crossed into water + SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + } + ent->v.watertype = cont; + ent->v.waterlevel = 1; + } + else + { + if (ent->v.watertype != CONTENTS_EMPTY) + { // just crossed into water + SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + } + ent->v.watertype = CONTENTS_EMPTY; + ent->v.waterlevel = cont; + } +} + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + + // regular thinking + if (!SV_RunThink (ent)) + return; + +// if onground, return without moving + if ( ((int)ent->v.flags & FL_ONGROUND) ) + return; + + SV_CheckVelocity (ent); + +// add gravity + if (ent->v.movetype != MOVETYPE_FLY + && ent->v.movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->v.angles, host_frametime, ent->v.avelocity, ent->v.angles); + +// move origin + VectorScale (ent->v.velocity, host_frametime, move); + trace = SV_PushEntity (ent, move); + if (trace.fraction == 1) + return; + if (ent->free) + return; + + if (ent->v.movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->v.velocity, trace.plane.normal, ent->v.velocity, backoff); + +// stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->v.velocity[2] < 60 || ent->v.movetype != MOVETYPE_BOUNCE) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + VectorCopy (vec3_origin, ent->v.velocity); + VectorCopy (vec3_origin, ent->v.avelocity); + } + } + +// 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 (edict_t *ent) +{ + qboolean hitsound; + +// freefall if not onground + if ( ! ((int)ent->v.flags & (FL_ONGROUND | FL_FLY | FL_SWIM) ) ) + { + if (ent->v.velocity[2] < sv_gravity.value*-0.1) + hitsound = true; + else + hitsound = false; + + SV_AddGravity (ent); + SV_CheckVelocity (ent); + SV_FlyMove (ent, host_frametime, NULL); + SV_LinkEdict (ent, true); + + if ( (int)ent->v.flags & FL_ONGROUND ) // just hit ground + { + if (hitsound) + SV_StartSound (ent, 0, "demon/dland2.wav", 255, 1); + } + } + +// regular thinking + SV_RunThink (ent); + + SV_CheckWaterTransition (ent); +} + + +//============================================================================ + +/* +================ +SV_Physics + +================ +*/ +void SV_Physics (void) +{ + int i; + int entity_cap; // For sv_freezenonclients + edict_t *ent; + +// let the progs know that a new frame has started + pr_global_struct->self = EDICT_TO_PROG(sv.edicts); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + pr_global_struct->time = sv.time; + PR_ExecuteProgram (pr_global_struct->StartFrame); + +//SV_CheckAllEnts (); + +// +// treat each object in turn +// + ent = sv.edicts; + + if (sv_freezenonclients.value) + entity_cap = svs.maxclients + 1; // Only run physics on clients and the world + else + entity_cap = sv.num_edicts; + + //for (i=0 ; ifree) + continue; + + if (pr_global_struct->force_retouch) + { + SV_LinkEdict (ent, true); // force retouch even for stationary + } + + if (i > 0 && i <= svs.maxclients) + SV_Physics_Client (ent, i); + else if (ent->v.movetype == MOVETYPE_PUSH) + SV_Physics_Pusher (ent); + else if (ent->v.movetype == MOVETYPE_NONE) + SV_Physics_None (ent); + else if (ent->v.movetype == MOVETYPE_NOCLIP) + SV_Physics_Noclip (ent); + else if (ent->v.movetype == MOVETYPE_STEP) + SV_Physics_Step (ent); + else if (ent->v.movetype == MOVETYPE_TOSS + || ent->v.movetype == MOVETYPE_BOUNCE + || ent->v.movetype == MOVETYPE_FLY + || ent->v.movetype == MOVETYPE_FLYMISSILE) + SV_Physics_Toss (ent); + else + Sys_Error ("SV_Physics: bad movetype %i", (int)ent->v.movetype); + } + + if (pr_global_struct->force_retouch) + pr_global_struct->force_retouch--; + + if (!sv_freezenonclients.value) + sv.time += host_frametime; +} diff --git a/source/sv_user.c b/source/sv_user.c new file mode 100644 index 0000000..c3e4cb5 --- /dev/null +++ b/source/sv_user.c @@ -0,0 +1,627 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +edict_t *sv_player; + +extern cvar_t sv_friction; +cvar_t sv_edgefriction = {"edgefriction", "2", CVAR_NONE}; +extern cvar_t sv_stopspeed; + +static vec3_t forward, right, up; + +// world +float *angles; +float *origin; +float *velocity; + +qboolean onground; + +usercmd_t cmd; + +cvar_t sv_idealpitchscale = {"sv_idealpitchscale","0.8",CVAR_NONE}; +cvar_t sv_altnoclip = {"sv_altnoclip","1",CVAR_ARCHIVE}; //johnfitz + +/* +=============== +SV_SetIdealPitch +=============== +*/ +#define MAX_FORWARD 6 +void SV_SetIdealPitch (void) +{ + float angleval, sinval, cosval; + trace_t tr; + vec3_t top, bottom; + float z[MAX_FORWARD]; + int i, j; + int step, dir, steps; + + if (!((int)sv_player->v.flags & FL_ONGROUND)) + return; + + angleval = sv_player->v.angles[YAW] * M_PI*2 / 360; + sinval = sin(angleval); + cosval = cos(angleval); + + for (i=0 ; iv.origin[0] + cosval*(i+3)*12; + top[1] = sv_player->v.origin[1] + sinval*(i+3)*12; + top[2] = sv_player->v.origin[2] + sv_player->v.view_ofs[2]; + + bottom[0] = top[0]; + bottom[1] = top[1]; + bottom[2] = top[2] - 160; + + tr = SV_Move (top, vec3_origin, vec3_origin, bottom, 1, sv_player); + if (tr.allsolid) + return; // looking at a wall, leave ideal the way is was + + if (tr.fraction == 1) + return; // near a dropoff + + z[i] = top[2] + tr.fraction*(bottom[2]-top[2]); + } + + dir = 0; + steps = 0; + for (j=1 ; j -ON_EPSILON && step < ON_EPSILON) + continue; + + if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) + return; // mixed changes + + steps++; + dir = step; + } + + if (!dir) + { + sv_player->v.idealpitch = 0; + return; + } + + if (steps < 2) + return; + sv_player->v.idealpitch = -dir * sv_idealpitchscale.value; +} + + +/* +================== +SV_UserFriction + +================== +*/ +void SV_UserFriction (void) +{ + float *vel; + float speed, newspeed, control; + vec3_t start, stop; + float friction; + trace_t trace; + + vel = velocity; + + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (!speed) + return; + +// if the leading edge is over a dropoff, increase friction + start[0] = stop[0] = origin[0] + vel[0]/speed*16; + start[1] = stop[1] = origin[1] + vel[1]/speed*16; + start[2] = origin[2] + sv_player->v.mins[2]; + stop[2] = start[2] - 34; + + trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, sv_player); + + 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 - host_frametime*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + +/* +============== +SV_Accelerate +============== +*/ +cvar_t sv_maxspeed = {"sv_maxspeed", "320", CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_accelerate = {"sv_accelerate", "10", CVAR_NONE}; +void SV_Accelerate (float wishspeed, const vec3_t wishdir) +{ + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = sv_accelerate.value*host_frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed*wishdir[i]; +} + +void SV_AirAccelerate (float wishspeed, vec3_t wishveloc) +{ + int i; + float addspeed, wishspd, accelspeed, currentspeed; + + wishspd = VectorNormalize (wishveloc); + if (wishspd > 30) + wishspd = 30; + currentspeed = DotProduct (velocity, wishveloc); + addspeed = wishspd - currentspeed; + if (addspeed <= 0) + return; +// accelspeed = sv_accelerate.value * host_frametime; + accelspeed = sv_accelerate.value*wishspeed * host_frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed*wishveloc[i]; +} + + +void DropPunchAngle (void) +{ + float len; + + len = VectorNormalize (sv_player->v.punchangle); + + len -= 10*host_frametime; + if (len < 0) + len = 0; + VectorScale (sv_player->v.punchangle, len, sv_player->v.punchangle); +} + +/* +=================== +SV_WaterMove + +=================== +*/ +void SV_WaterMove (void) +{ + int i; + vec3_t wishvel; + float speed, newspeed, wishspeed, addspeed, accelspeed; + +// +// user intentions +// + AngleVectors (sv_player->v.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) + { + VectorScale (wishvel, sv_maxspeed.value/wishspeed, wishvel); + wishspeed = sv_maxspeed.value; + } + wishspeed *= 0.7; + +// +// water friction +// + speed = VectorLength (velocity); + if (speed) + { + newspeed = speed - host_frametime * speed * sv_friction.value; + if (newspeed < 0) + newspeed = 0; + VectorScale (velocity, newspeed/speed, velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if (!wishspeed) + return; + + addspeed = wishspeed - newspeed; + if (addspeed <= 0) + return; + + VectorNormalize (wishvel); + accelspeed = sv_accelerate.value * wishspeed * host_frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed * wishvel[i]; +} + +void SV_WaterJump (void) +{ + if (sv.time > sv_player->v.teleport_time + || !sv_player->v.waterlevel) + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_WATERJUMP; + sv_player->v.teleport_time = 0; + } + sv_player->v.velocity[0] = sv_player->v.movedir[0]; + sv_player->v.velocity[1] = sv_player->v.movedir[1]; +} + +/* +=================== +SV_NoclipMove -- johnfitz + +new, alternate noclip. old noclip is still handled in SV_AirMove +=================== +*/ +void SV_NoclipMove (void) +{ + AngleVectors (sv_player->v.v_angle, forward, right, up); + + velocity[0] = forward[0]*cmd.forwardmove + right[0]*cmd.sidemove; + velocity[1] = forward[1]*cmd.forwardmove + right[1]*cmd.sidemove; + velocity[2] = forward[2]*cmd.forwardmove + right[2]*cmd.sidemove; + velocity[2] += cmd.upmove*2; //doubled to match running speed + + if (VectorLength (velocity) > sv_maxspeed.value) + { + VectorNormalize (velocity); + VectorScale (velocity, sv_maxspeed.value, velocity); + } +} + +/* +=================== +SV_AirMove +=================== +*/ +void SV_AirMove (void) +{ + int i; + vec3_t wishvel, wishdir; + float wishspeed; + float fmove, smove; + + AngleVectors (sv_player->v.angles, forward, right, up); + + fmove = cmd.forwardmove; + smove = cmd.sidemove; + +// hack to not let you back into teleporter + if (sv.time < sv_player->v.teleport_time && fmove < 0) + fmove = 0; + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*fmove + right[i]*smove; + + if ( (int)sv_player->v.movetype != MOVETYPE_WALK) + wishvel[2] = cmd.upmove; + else + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + if (wishspeed > sv_maxspeed.value) + { + VectorScale (wishvel, sv_maxspeed.value/wishspeed, wishvel); + wishspeed = sv_maxspeed.value; + } + + if ( sv_player->v.movetype == MOVETYPE_NOCLIP) + { // noclip + VectorCopy (wishvel, velocity); + } + else if ( onground ) + { + SV_UserFriction (); + SV_Accelerate (wishspeed, wishdir); + } + else + { // not on ground, so little effect on velocity + SV_AirAccelerate (wishspeed, 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; + + if (sv_player->v.movetype == MOVETYPE_NONE) + return; + + onground = (int)sv_player->v.flags & FL_ONGROUND; + + origin = sv_player->v.origin; + velocity = sv_player->v.velocity; + + DropPunchAngle (); + +// +// if dead, behave differently +// + if (sv_player->v.health <= 0) + return; + +// +// angles +// show 1/3 the pitch angle and all the roll angle + cmd = host_client->cmd; + angles = sv_player->v.angles; + + VectorAdd (sv_player->v.v_angle, sv_player->v.punchangle, v_angle); + angles[ROLL] = V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4; + if (!sv_player->v.fixangle) + { + angles[PITCH] = -v_angle[PITCH]/3; + angles[YAW] = v_angle[YAW]; + } + + if ( (int)sv_player->v.flags & FL_WATERJUMP ) + { + SV_WaterJump (); + return; + } +// +// walk +// + //johnfitz -- alternate noclip + if (sv_player->v.movetype == MOVETYPE_NOCLIP && sv_altnoclip.value) + SV_NoclipMove (); + else if (sv_player->v.waterlevel >= 2 && sv_player->v.movetype != MOVETYPE_NOCLIP) + SV_WaterMove (); + else + SV_AirMove (); + //johnfitz +} + + +/* +=================== +SV_ReadClientMove +=================== +*/ +void SV_ReadClientMove (usercmd_t *move) +{ + int i; + vec3_t angle; + int bits; + +// read ping time + host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] + = sv.time - MSG_ReadFloat (); + host_client->num_pings++; + +// read current angles + for (i=0 ; i<3 ; i++) + //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + if (sv.protocol == PROTOCOL_NETQUAKE) + angle[i] = MSG_ReadAngle (sv.protocolflags); + else + angle[i] = MSG_ReadAngle16 (sv.protocolflags); + //johnfitz + + VectorCopy (angle, host_client->edict->v.v_angle); + +// read movement + move->forwardmove = MSG_ReadShort (); + move->sidemove = MSG_ReadShort (); + move->upmove = MSG_ReadShort (); + +// read buttons + bits = MSG_ReadByte (); + host_client->edict->v.button0 = bits & 1; + host_client->edict->v.button2 = (bits & 2)>>1; + + i = MSG_ReadByte (); + if (i) + host_client->edict->v.impulse = i; +} + +/* +=================== +SV_ReadClientMessage + +Returns false if the client should be killed +=================== +*/ +qboolean SV_ReadClientMessage (void) +{ + int ret; + int ccmd; + const char *s; + + do + { +nextmsg: + ret = NET_GetMessage (host_client->netconnection); + if (ret == -1) + { + Sys_Printf ("SV_ReadClientMessage: NET_GetMessage failed\n"); + return false; + } + if (!ret) + return true; + + MSG_BeginReading (); + + while (1) + { + if (!host_client->active) + return false; // a command caused an error + + if (msg_badread) + { + Sys_Printf ("SV_ReadClientMessage: badread\n"); + return false; + } + + ccmd = MSG_ReadChar (); + + switch (ccmd) + { + case -1: + goto nextmsg; // end of message + + default: + Sys_Printf ("SV_ReadClientMessage: unknown command char\n"); + return false; + + case clc_nop: +// Sys_Printf ("clc_nop\n"); + break; + + case clc_stringcmd: + s = MSG_ReadString (); + ret = 0; + if (q_strncasecmp(s, "status", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "god", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "notarget", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "fly", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "name", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "noclip", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "setpos", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "say", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "say_team", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "tell", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "color", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "kill", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "pause", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "spawn", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "begin", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "prespawn", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "kick", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "ping", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "give", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "ban", 3) == 0) + ret = 1; + + if (ret == 1) + Cmd_ExecuteString (s, src_client); + else + Con_DPrintf("%s tried to %s\n", host_client->name, s); + break; + + case clc_disconnect: +// Sys_Printf ("SV_ReadClientMessage: client disconnected\n"); + return false; + + case clc_move: + SV_ReadClientMove (&host_client->cmd); + break; + } + } + } while (ret == 1); + + return true; +} + + +/* +================== +SV_RunClients +================== +*/ +void SV_RunClients (void) +{ + int i; + + for (i=0, host_client = svs.clients ; iactive) + continue; + + sv_player = host_client->edict; + + if (!SV_ReadClientMessage ()) + { + SV_DropClient (false); // client misbehaved... + continue; + } + + if (!host_client->spawned) + { + // clear client movement until a new packet is received + memset (&host_client->cmd, 0, sizeof(host_client->cmd)); + continue; + } + +// always pause in single player if in console or menus + if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) ) + SV_ClientThink (); + } +} + diff --git a/source/sys.h b/source/sys.h new file mode 100644 index 0000000..dd02363 --- /dev/null +++ b/source/sys.h @@ -0,0 +1,70 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_SYS_H +#define _QUAKE_SYS_H + +// sys.h -- non-portable functions + +void Sys_Init (void); + +// +// file IO +// + +// returns the file size or -1 if file is not present. +// the file should be in BINARY mode for stupid OSs that care +int Sys_FileOpenRead (const char *path, int *hndl); + +int Sys_FileOpenWrite (const char *path); +void Sys_FileClose (int handle); +void Sys_FileSeek (int handle, int position); +int Sys_FileRead (int handle, void *dest, int count); +int Sys_FileWrite (int handle,const void *data, int count); +int Sys_FileTime (const char *path); +void Sys_mkdir (const char *path); + +// +// system IO +// +FUNC_NORETURN void Sys_Quit (void); +FUNC_NORETURN void Sys_Error (const char *error, ...) FUNC_PRINTF(1,2); +// an error will cause the entire program to exit +#ifdef __WATCOMC__ +#pragma aux Sys_Error aborts; +#pragma aux Sys_Quit aborts; +#endif + +void Sys_Printf (const char *fmt, ...) FUNC_PRINTF(1,2); +// send text to the console + +double Sys_DoubleTime (void); + +const char *Sys_ConsoleInput (void); + +void Sys_Sleep (unsigned long msecs); +// yield for about 'msecs' milliseconds. + +void Sys_SendKeyEvents (void); +// Perform Key_Event () callbacks until the input que is empty + +#endif /* _QUAKE_SYS_H */ + diff --git a/source/sys_sdl_unix.c b/source/sys_sdl_unix.c new file mode 100644 index 0000000..5338a69 --- /dev/null +++ b/source/sys_sdl_unix.c @@ -0,0 +1,466 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 "arch_def.h" +#include "quakedef.h" + +#include +#include +#include +#ifdef PLATFORM_OSX +#include /* dirname() and basename() */ +#endif +#include +#include +#include +#include +#ifdef DO_USERDIRS +#include +#endif + +#include + +qboolean isDedicated; +cvar_t sys_throttle = {"sys_throttle", "0.02", CVAR_ARCHIVE}; + +#define MAX_HANDLES 32 /* johnfitz -- was 10 */ +static FILE *sys_handles[MAX_HANDLES]; + + +static int findhandle (void) +{ + int i; + + for (i = 1; i < MAX_HANDLES; i++) + { + if (!sys_handles[i]) + return i; + } + Sys_Error ("out of handles"); + return -1; +} + +long Sys_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +int Sys_FileOpenRead (const char *path, int *hndl) +{ + FILE *f; + int i, retval; + + i = findhandle (); + f = fopen(path, "rb"); + + if (!f) + { + *hndl = -1; + retval = -1; + } + else + { + sys_handles[i] = f; + *hndl = i; + retval = Sys_filelength(f); + } + + return retval; +} + +int Sys_FileOpenWrite (const char *path) +{ + FILE *f; + int i; + + i = findhandle (); + f = fopen(path, "wb"); + + if (!f) + Sys_Error ("Error opening %s: %s", path, strerror(errno)); + + sys_handles[i] = f; + return i; +} + +void Sys_FileClose (int handle) +{ + fclose (sys_handles[handle]); + sys_handles[handle] = NULL; +} + +void Sys_FileSeek (int handle, int position) +{ + fseek (sys_handles[handle], position, SEEK_SET); +} + +int Sys_FileRead (int handle, void *dest, int count) +{ + return fread (dest, 1, count, sys_handles[handle]); +} + +int Sys_FileWrite (int handle, const void *data, int count) +{ + return fwrite (data, 1, count, sys_handles[handle]); +} + +int Sys_FileTime (const char *path) +{ + FILE *f; + + f = fopen(path, "rb"); + + if (f) + { + fclose(f); + return 1; + } + + return -1; +} + + +#if defined(__linux__) || defined(__sun) || defined(sun) || defined(_AIX) +static int Sys_NumCPUs (void) +{ + int numcpus = sysconf(_SC_NPROCESSORS_ONLN); + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(PLATFORM_OSX) +#include +#if !defined(HW_AVAILCPU) /* using an ancient SDK? */ +#define HW_AVAILCPU 25 /* needs >= 10.2 */ +#endif +static int Sys_NumCPUs (void) +{ + int numcpus; + int mib[2]; + size_t len; + +#if defined(_SC_NPROCESSORS_ONLN) /* needs >= 10.5 */ + numcpus = sysconf(_SC_NPROCESSORS_ONLN); + if (numcpus != -1) + return (numcpus < 1) ? 1 : numcpus; +#endif + len = sizeof(numcpus); + mib[0] = CTL_HW; + mib[1] = HW_AVAILCPU; + sysctl(mib, 2, &numcpus, &len, NULL, 0); + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + { + mib[1] = HW_NCPU; + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + return 1; + } + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(__sgi) || defined(sgi) || defined(__sgi__) /* IRIX */ +static int Sys_NumCPUs (void) +{ + int numcpus = sysconf(_SC_NPROC_ONLN); + if (numcpus < 1) + numcpus = 1; + return numcpus; +} + +#elif defined(PLATFORM_BSD) +#include +static int Sys_NumCPUs (void) +{ + int numcpus; + int mib[2]; + size_t len; + +#if defined(_SC_NPROCESSORS_ONLN) + numcpus = sysconf(_SC_NPROCESSORS_ONLN); + if (numcpus != -1) + return (numcpus < 1) ? 1 : numcpus; +#endif + len = sizeof(numcpus); + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + return 1; + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(__hpux) || defined(__hpux__) || defined(_hpux) +#include +static int Sys_NumCPUs (void) +{ + int numcpus = mpctl(MPC_GETNUMSPUS, NULL, NULL); + return numcpus; +} + +#else /* unknown OS */ +static int Sys_NumCPUs (void) +{ + return -2; +} +#endif + +static char cwd[MAX_OSPATH]; +#ifdef DO_USERDIRS +static char userdir[MAX_OSPATH]; +#ifdef PLATFORM_OSX +#define SYS_USERDIR "Library/Application Support/QuakeSpasm" +#else +#define SYS_USERDIR ".quakespasm" +#endif + +static void Sys_GetUserdir (char *dst, size_t dstsize) +{ + size_t n; + const char *home_dir = NULL; + struct passwd *pwent; + + pwent = getpwuid( getuid() ); + if (pwent == NULL) + perror("getpwuid"); + else + home_dir = pwent->pw_dir; + if (home_dir == NULL) + home_dir = getenv("HOME"); + if (home_dir == NULL) + Sys_Error ("Couldn't determine userspace directory"); + +/* what would be a maximum path for a file in the user's directory... + * $HOME/SYS_USERDIR/game_dir/dirname1/dirname2/dirname3/filename.ext + * still fits in the MAX_OSPATH == 256 definition, but just in case : + */ + n = strlen(home_dir) + strlen(SYS_USERDIR) + 50; + if (n >= dstsize) + Sys_Error ("Insufficient array size for userspace directory"); + + q_snprintf (dst, dstsize, "%s/%s", home_dir, SYS_USERDIR); +} +#endif /* DO_USERDIRS */ + +#ifdef PLATFORM_OSX +static char *OSX_StripAppBundle (char *dir) +{ /* based on the ioquake3 project at icculus.org. */ + static char osx_path[MAX_OSPATH]; + + q_strlcpy (osx_path, dir, sizeof(osx_path)); + if (strcmp(basename(osx_path), "MacOS")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + if (strcmp(basename(osx_path), "Contents")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + if (!strstr(basename(osx_path), ".app")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + return osx_path; +} + +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + + if (realpath(argv0, dst) == NULL) + { + perror("realpath"); + if (getcwd(dst, dstsize - 1) == NULL) + _fail: Sys_Error ("Couldn't determine current directory"); + } + else + { + /* strip off the binary name */ + if (! (tmp = strdup (dst))) goto _fail; + q_strlcpy (dst, dirname(tmp), dstsize); + free (tmp); + } + + tmp = OSX_StripAppBundle(dst); + if (tmp != dst) + q_strlcpy (dst, tmp, dstsize); +} +#else +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + + if (getcwd(dst, dstsize - 1) == NULL) + Sys_Error ("Couldn't determine current directory"); + + tmp = dst; + while (*tmp != 0) + tmp++; + while (*tmp == 0 && tmp != dst) + { + --tmp; + if (tmp != dst && *tmp == '/') + *tmp = 0; + } +} +#endif + +void Sys_Init (void) +{ + memset (cwd, 0, sizeof(cwd)); + Sys_GetBasedir(host_parms->argv[0], cwd, sizeof(cwd)); + host_parms->basedir = cwd; +#ifndef DO_USERDIRS + host_parms->userdir = host_parms->basedir; /* code elsewhere relies on this ! */ +#else + memset (userdir, 0, sizeof(userdir)); + Sys_GetUserdir(userdir, sizeof(userdir)); + Sys_mkdir (userdir); + host_parms->userdir = userdir; +#endif + host_parms->numcpus = Sys_NumCPUs (); + Sys_Printf("Detected %d CPUs.\n", host_parms->numcpus); +} + +void Sys_mkdir (const char *path) +{ + int rc = mkdir (path, 0777); + if (rc != 0 && errno == EEXIST) + { + struct stat st; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + rc = 0; + } + if (rc != 0) + { + rc = errno; + Sys_Error("Unable to create directory %s: %s", path, strerror(rc)); + } +} + +static const char errortxt1[] = "\nERROR-OUT BEGIN\n\n"; +static const char errortxt2[] = "\nQUAKE ERROR: "; + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char text[1024]; + + host_parms->errstate++; + + va_start (argptr, error); + q_vsnprintf (text, sizeof(text), error, argptr); + va_end (argptr); + + fputs (errortxt1, stderr); + Host_Shutdown (); + fputs (errortxt2, stderr); + fputs (text, stderr); + fputs ("\n\n", stderr); + if (!isDedicated) + PL_ErrorDialog(text); + + exit (1); +} + +void Sys_Printf (const char *fmt, ...) +{ + va_list argptr; + + va_start(argptr, fmt); + vprintf(fmt, argptr); + va_end(argptr); +} + +void Sys_Quit (void) +{ + Host_Shutdown(); + + exit (0); +} + +double Sys_DoubleTime (void) +{ + return SDL_GetTicks() / 1000.0; +} + +const char *Sys_ConsoleInput (void) +{ + static char con_text[256]; + static int textlen; + char c; + fd_set set; + struct timeval timeout; + + FD_ZERO (&set); + FD_SET (0, &set); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + while (select (1, &set, NULL, NULL, &timeout)) + { + read (0, &c, 1); + if (c == '\n' || c == '\r') + { + con_text[textlen] = '\0'; + textlen = 0; + return con_text; + } + else if (c == 8) + { + if (textlen) + { + textlen--; + con_text[textlen] = '\0'; + } + continue; + } + con_text[textlen] = c; + textlen++; + if (textlen < (int) sizeof(con_text)) + con_text[textlen] = '\0'; + else + { + // buffer is full + textlen = 0; + con_text[0] = '\0'; + Sys_Printf("\nConsole input too long!\n"); + break; + } + } + + return NULL; +} + +void Sys_Sleep (unsigned long msecs) +{ +/* usleep (msecs * 1000);*/ + SDL_Delay (msecs); +} + +void Sys_SendKeyEvents (void) +{ + IN_Commands(); //ericw -- allow joysticks to add keys so they can be used to confirm SCR_ModalMessage + IN_SendKeyEvents(); +} + diff --git a/source/sys_sdl_win.c b/source/sys_sdl_win.c new file mode 100644 index 0000000..75a2083 --- /dev/null +++ b/source/sys_sdl_win.c @@ -0,0 +1,434 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +#include "quakedef.h" + +#include +#include +#include +#include + +#include + +qboolean isDedicated; +qboolean Win95, Win95old, WinNT, WinVista; +cvar_t sys_throttle = {"sys_throttle", "0.02", CVAR_ARCHIVE}; + +static HANDLE hinput, houtput; + +#define MAX_HANDLES 32 /* johnfitz -- was 10 */ +static FILE *sys_handles[MAX_HANDLES]; + + +static int findhandle (void) +{ + int i; + + for (i = 1; i < MAX_HANDLES; i++) + { + if (!sys_handles[i]) + return i; + } + Sys_Error ("out of handles"); + return -1; +} + +long Sys_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +int Sys_FileOpenRead (const char *path, int *hndl) +{ + FILE *f; + int i, retval; + + i = findhandle (); + f = fopen(path, "rb"); + + if (!f) + { + *hndl = -1; + retval = -1; + } + else + { + sys_handles[i] = f; + *hndl = i; + retval = Sys_filelength(f); + } + + return retval; +} + +int Sys_FileOpenWrite (const char *path) +{ + FILE *f; + int i; + + i = findhandle (); + f = fopen(path, "wb"); + + if (!f) + Sys_Error ("Error opening %s: %s", path, strerror(errno)); + + sys_handles[i] = f; + return i; +} + +void Sys_FileClose (int handle) +{ + fclose (sys_handles[handle]); + sys_handles[handle] = NULL; +} + +void Sys_FileSeek (int handle, int position) +{ + fseek (sys_handles[handle], position, SEEK_SET); +} + +int Sys_FileRead (int handle, void *dest, int count) +{ + return fread (dest, 1, count, sys_handles[handle]); +} + +int Sys_FileWrite (int handle, const void *data, int count) +{ + return fwrite (data, 1, count, sys_handles[handle]); +} + +int Sys_FileTime (const char *path) +{ + FILE *f; + + f = fopen(path, "rb"); + + if (f) + { + fclose(f); + return 1; + } + + return -1; +} + +static char cwd[1024]; + +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + size_t rc; + + rc = GetCurrentDirectory(dstsize, dst); + if (rc == 0 || rc > dstsize) + Sys_Error ("Couldn't determine current directory"); + + tmp = dst; + while (*tmp != 0) + tmp++; + while (*tmp == 0 && tmp != dst) + { + --tmp; + if (tmp != dst && (*tmp == '/' || *tmp == '\\')) + *tmp = 0; + } +} + +typedef enum { dpi_unaware = 0, dpi_system_aware = 1, dpi_monitor_aware = 2 } dpi_awareness; +typedef BOOL (WINAPI *SetProcessDPIAwareFunc)(); +typedef HRESULT (WINAPI *SetProcessDPIAwarenessFunc)(dpi_awareness value); + +static void Sys_SetDPIAware (void) +{ + HMODULE hUser32, hShcore; + SetProcessDPIAwarenessFunc setDPIAwareness; + SetProcessDPIAwareFunc setDPIAware; + + /* Neither SDL 1.2 nor SDL 2.0.3 can handle the OS scaling our window. + (e.g. https://bugzilla.libsdl.org/show_bug.cgi?id=2713) + Call SetProcessDpiAwareness/SetProcessDPIAware to opt out of scaling. + */ + + hShcore = LoadLibraryA ("Shcore.dll"); + hUser32 = LoadLibraryA ("user32.dll"); + setDPIAwareness = (SetProcessDPIAwarenessFunc) (hShcore ? GetProcAddress (hShcore, "SetProcessDpiAwareness") : NULL); + setDPIAware = (SetProcessDPIAwareFunc) (hUser32 ? GetProcAddress (hUser32, "SetProcessDPIAware") : NULL); + + if (setDPIAwareness) /* Windows 8.1+ */ + setDPIAwareness (dpi_monitor_aware); + else if (setDPIAware) /* Windows Vista-8.0 */ + setDPIAware (); + + if (hShcore) + FreeLibrary (hShcore); + if (hUser32) + FreeLibrary (hUser32); +} + +static void Sys_SetTimerResolution(void) +{ + /* Set OS timer resolution to 1ms. + Works around buffer underruns with directsound and SDL2, but also + will make Sleep()/SDL_Dleay() accurate to 1ms which should help framerate + stability. + */ + timeBeginPeriod (1); +} + +void Sys_Init (void) +{ + OSVERSIONINFO vinfo; + + Sys_SetTimerResolution (); + Sys_SetDPIAware (); + + memset (cwd, 0, sizeof(cwd)); + Sys_GetBasedir(NULL, cwd, sizeof(cwd)); + host_parms->basedir = cwd; + + /* userdirs not really necessary for windows guys. + * can be done if necessary, though... */ + host_parms->userdir = host_parms->basedir; /* code elsewhere relies on this ! */ + + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + + if (!GetVersionEx (&vinfo)) + Sys_Error ("Couldn't get OS info"); + + if ((vinfo.dwMajorVersion < 4) || + (vinfo.dwPlatformId == VER_PLATFORM_WIN32s)) + { + Sys_Error ("QuakeSpasm requires at least Win95 or NT 4.0"); + } + + if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + SYSTEM_INFO info; + WinNT = true; + if (vinfo.dwMajorVersion >= 6) + WinVista = true; + GetSystemInfo(&info); + host_parms->numcpus = info.dwNumberOfProcessors; + if (host_parms->numcpus < 1) + host_parms->numcpus = 1; + } + else + { + WinNT = false; /* Win9x or WinME */ + host_parms->numcpus = 1; + if ((vinfo.dwMajorVersion == 4) && (vinfo.dwMinorVersion == 0)) + { + Win95 = true; + /* Win95-gold or Win95A can't switch bpp automatically */ + if (vinfo.szCSDVersion[1] != 'C' && vinfo.szCSDVersion[1] != 'B') + Win95old = true; + } + } + Sys_Printf("Detected %d CPUs.\n", host_parms->numcpus); + + if (isDedicated) + { + if (!AllocConsole ()) + { + isDedicated = false; /* so that we have a graphical error dialog */ + Sys_Error ("Couldn't create dedicated server console"); + } + + hinput = GetStdHandle (STD_INPUT_HANDLE); + houtput = GetStdHandle (STD_OUTPUT_HANDLE); + } +} + +void Sys_mkdir (const char *path) +{ + if (CreateDirectory(path, NULL) != 0) + return; + if (GetLastError() != ERROR_ALREADY_EXISTS) + Sys_Error("Unable to create directory %s", path); +} + +static const char errortxt1[] = "\nERROR-OUT BEGIN\n\n"; +static const char errortxt2[] = "\nQUAKE ERROR: "; + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char text[1024]; + DWORD dummy; + + host_parms->errstate++; + + va_start (argptr, error); + q_vsnprintf (text, sizeof(text), error, argptr); + va_end (argptr); + + if (isDedicated) + WriteFile (houtput, errortxt1, strlen(errortxt1), &dummy, NULL); + /* SDL will put these into its own stderr log, + so print to stderr even in graphical mode. */ + fputs (errortxt1, stderr); + Host_Shutdown (); + fputs (errortxt2, stderr); + fputs (text, stderr); + fputs ("\n\n", stderr); + if (!isDedicated) + PL_ErrorDialog(text); + else + { + WriteFile (houtput, errortxt2, strlen(errortxt2), &dummy, NULL); + WriteFile (houtput, text, strlen(text), &dummy, NULL); + WriteFile (houtput, "\r\n", 2, &dummy, NULL); + SDL_Delay (3000); /* show the console 3 more seconds */ + } + + exit (1); +} + +void Sys_Printf (const char *fmt, ...) +{ + va_list argptr; + char text[1024]; + DWORD dummy; + + va_start (argptr,fmt); + q_vsnprintf (text, sizeof(text), fmt, argptr); + va_end (argptr); + + if (isDedicated) + { + WriteFile(houtput, text, strlen(text), &dummy, NULL); + } + else + { + /* SDL will put these into its own stdout log, + so print to stdout even in graphical mode. */ + fputs (text, stdout); + } +} + +void Sys_Quit (void) +{ + Host_Shutdown(); + + if (isDedicated) + FreeConsole (); + + exit (0); +} + +double Sys_DoubleTime (void) +{ + return SDL_GetTicks() / 1000.0; +} + +const char *Sys_ConsoleInput (void) +{ + static char con_text[256]; + static int textlen; + INPUT_RECORD recs[1024]; + int ch; + DWORD dummy, numread, numevents; + + for ( ;; ) + { + if (GetNumberOfConsoleInputEvents(hinput, &numevents) == 0) + Sys_Error ("Error getting # of console events"); + + if (! numevents) + break; + + if (ReadConsoleInput(hinput, recs, 1, &numread) == 0) + Sys_Error ("Error reading console input"); + + if (numread != 1) + Sys_Error ("Couldn't read console input"); + + if (recs[0].EventType == KEY_EVENT) + { + if (recs[0].Event.KeyEvent.bKeyDown == FALSE) + { + ch = recs[0].Event.KeyEvent.uChar.AsciiChar; + + switch (ch) + { + case '\r': + WriteFile(houtput, "\r\n", 2, &dummy, NULL); + + if (textlen != 0) + { + con_text[textlen] = 0; + textlen = 0; + return con_text; + } + + break; + + case '\b': + WriteFile(houtput, "\b \b", 3, &dummy, NULL); + if (textlen != 0) + textlen--; + + break; + + default: + if (ch >= ' ') + { + WriteFile(houtput, &ch, 1, &dummy, NULL); + con_text[textlen] = ch; + textlen = (textlen + 1) & 0xff; + } + + break; + } + } + } + } + + return NULL; +} + +void Sys_Sleep (unsigned long msecs) +{ +/* Sleep (msecs);*/ + SDL_Delay (msecs); +} + +void Sys_SendKeyEvents (void) +{ + IN_Commands(); //ericw -- allow joysticks to add keys so they can be used to confirm SCR_ModalMessage + IN_SendKeyEvents(); +} + diff --git a/source/vid.h b/source/vid.h new file mode 100644 index 0000000..46ae8d6 --- /dev/null +++ b/source/vid.h @@ -0,0 +1,93 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __VID_DEFS_H +#define __VID_DEFS_H + +// vid.h -- video driver defs + +#define VID_CBITS 6 +#define VID_GRADES (1 << VID_CBITS) + +#define GAMMA_MAX 3.0 + +// moved here for global use -- kristian +typedef enum { MS_UNINIT, MS_WINDOWED, MS_FULLSCREEN } modestate_t; + +extern modestate_t modestate; + +// a pixel can be one, two, or four bytes +typedef byte pixel_t; + +typedef struct vrect_s +{ + int x, y, width, height; + struct vrect_s *pnext; +} vrect_t; + +typedef struct +{ + pixel_t *buffer; // invisible buffer + pixel_t *colormap; // 256 * VID_GRADES size + unsigned short *colormap16; // 256 * VID_GRADES size + int fullbright; // index of first fullbright color + int rowbytes; // may be > width if displayed in a window + int width; + int height; + float aspect; // width / height -- < 0 is taller than wide + int numpages; + int recalc_refdef; // if true, recalc vid-based stuff + pixel_t *conbuffer; + int conrowbytes; + int conwidth; + int conheight; + int maxwarpwidth; + int maxwarpheight; + pixel_t *direct; // direct drawing to framebuffer, if not NULL +} viddef_t; + +extern viddef_t vid; // global video state + +extern void (*vid_menudrawfn)(void); +extern void (*vid_menukeyfn)(int key); +extern void (*vid_menucmdfn)(void); //johnfitz + +void VID_Init (void); //johnfitz -- removed palette from argument list + +void VID_Shutdown (void); +// Called at shutdown + +void VID_Update (vrect_t *rects); +// flushes the given rectangles from the view buffer to the screen + +void VID_SyncCvars (void); + +void VID_Toggle (void); + +void *VID_GetWindow (void); +qboolean VID_HasMouseOrInputFocus (void); +qboolean VID_IsMinimized (void); +void VID_Lock (void); + +#endif /* __VID_DEFS_H */ + diff --git a/source/view.c b/source/view.c new file mode 100644 index 0000000..27ed069 --- /dev/null +++ b/source/view.c @@ -0,0 +1,950 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +/* + +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 scr_ofsx = {"scr_ofsx","0", CVAR_NONE}; +cvar_t scr_ofsy = {"scr_ofsy","0", CVAR_NONE}; +cvar_t scr_ofsz = {"scr_ofsz","0", CVAR_NONE}; + +cvar_t cl_rollspeed = {"cl_rollspeed", "200", CVAR_NONE}; +cvar_t cl_rollangle = {"cl_rollangle", "2.0", CVAR_NONE}; + +cvar_t cl_bob = {"cl_bob","0.02", CVAR_NONE}; +cvar_t cl_bobcycle = {"cl_bobcycle","0.6", CVAR_NONE}; +cvar_t cl_bobup = {"cl_bobup","0.5", CVAR_NONE}; + +cvar_t v_kicktime = {"v_kicktime", "0.5", CVAR_NONE}; +cvar_t v_kickroll = {"v_kickroll", "0.6", CVAR_NONE}; +cvar_t v_kickpitch = {"v_kickpitch", "0.6", CVAR_NONE}; +cvar_t v_gunkick = {"v_gunkick", "1", CVAR_NONE}; //johnfitz + +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", CVAR_NONE}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", CVAR_NONE}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", CVAR_NONE}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", CVAR_NONE}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", CVAR_NONE}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", CVAR_NONE}; + +cvar_t v_idlescale = {"v_idlescale", "0", CVAR_NONE}; + +cvar_t crosshair = {"crosshair", "0", CVAR_ARCHIVE}; + +cvar_t gl_cshiftpercent = {"gl_cshiftpercent", "100", CVAR_NONE}; +cvar_t gl_cshiftpercent_contents = {"gl_cshiftpercent_contents", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_damage = {"gl_cshiftpercent_damage", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_bonus = {"gl_cshiftpercent_bonus", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_powerup = {"gl_cshiftpercent_powerup", "100", CVAR_NONE}; // QuakeSpasm + +cvar_t r_viewmodel_quake = {"r_viewmodel_quake", "0", CVAR_ARCHIVE}; + +float v_dmg_time, v_dmg_roll, v_dmg_pitch; + +extern int in_forward, in_forward2, in_back; + +vec3_t v_punchangles[2]; //johnfitz -- copied from cl.punchangle. 0 is current, 1 is previous value. never the same unless map just loaded + +/* +=============== +V_CalcRoll + +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity) +{ + vec3_t forward, right, up; + float sign; + float side; + float value; + + AngleVectors (angles, forward, right, up); + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = cl_rollangle.value; +// if (cl.inwater) +// value *= 6; + + if (side < cl_rollspeed.value) + side = side * value / cl_rollspeed.value; + else + side = value; + + return side*sign; +} + + +/* +=============== +V_CalcBob + +=============== +*/ +float V_CalcBob (void) +{ + float bob; + float cycle; + + cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value; + cycle /= cl_bobcycle.value; + if (cycle < cl_bobup.value) + cycle = M_PI * cycle / cl_bobup.value; + else + cycle = 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(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value; +//Con_Printf ("speed: %5.1f\n", VectorLength(cl.velocity)); + bob = bob*0.3 + bob*0.7*sin(cycle); + if (bob > 4) + bob = 4; + else if (bob < -7) + bob = -7; + return bob; +} + + +//============================================================================= + + +cvar_t v_centermove = {"v_centermove", "0.15", CVAR_NONE}; +cvar_t v_centerspeed = {"v_centerspeed","500", CVAR_NONE}; + + +void V_StartPitchDrift (void) +{ +#if 1 + if (cl.laststop == cl.time) + { + return; // something else is keeping it from drifting + } +#endif + 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 ) + //FIXME: noclip_anglehack is set on the server, so in a nonlocal game this won't work. + { + 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 += host_frametime; + + if ( cl.driftmove > v_centermove.value) + { + if (lookspring.value) + V_StartPitchDrift (); + } + return; + } + + delta = cl.idealpitch - cl.viewangles[PITCH]; + + if (!delta) + { + cl.pitchvel = 0; + return; + } + + move = host_frametime * cl.pitchvel; + cl.pitchvel += host_frametime * v_centerspeed.value; + +//Con_Printf ("move: %f (%f)\n", move, host_frametime); + + 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; + } +} + +/* +============================================================================== + + VIEW BLENDING + +============================================================================== +*/ + +cshift_t cshift_empty = { {130,80,50}, 0 }; +cshift_t cshift_water = { {130,80,50}, 128 }; +cshift_t cshift_slime = { {0,25,5}, 150 }; +cshift_t cshift_lava = { {255,80,0}, 150 }; + +float v_blend[4]; // rgba 0.0 - 1.0 + +//johnfitz -- deleted BuildGammaTable(), V_CheckGamma(), gammatable[], and ramps[][] + +/* +=============== +V_ParseDamage +=============== +*/ +void V_ParseDamage (void) +{ + int armor, blood; + vec3_t from; + int i; + vec3_t forward, right, up; + entity_t *ent; + float side; + float count; + + armor = MSG_ReadByte (); + blood = MSG_ReadByte (); + for (i=0 ; i<3 ; i++) + from[i] = MSG_ReadCoord (cl.protocolflags); + + count = blood*0.5 + armor*0.5; + if (count < 10) + count = 10; + + cl.faceanimtime = cl.time + 0.2; // but sbar face into pain frame + + cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; + 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 +// + ent = &cl_entities[cl.viewentity]; + + VectorSubtract (from, ent->origin, from); + VectorNormalize (from); + + AngleVectors (ent->angles, forward, right, up); + + side = DotProduct (from, right); + v_dmg_roll = count*side*v_kickroll.value; + + side = DotProduct (from, forward); + v_dmg_pitch = count*side*v_kickpitch.value; + + v_dmg_time = v_kicktime.value; +} + + +/* +================== +V_cshift_f +================== +*/ +void V_cshift_f (void) +{ + cshift_empty.destcolor[0] = atoi(Cmd_Argv(1)); + cshift_empty.destcolor[1] = atoi(Cmd_Argv(2)); + cshift_empty.destcolor[2] = atoi(Cmd_Argv(3)); + cshift_empty.percent = atoi(Cmd_Argv(4)); +} + + +/* +================== +V_BonusFlash_f + +When you run over an item, the server sends this command +================== +*/ +void V_BonusFlash_f (void) +{ + 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; +} + +/* +============= +V_SetContentsColor + +Underwater, lava, etc each has a color shift +============= +*/ +void V_SetContentsColor (int contents) +{ + switch (contents) + { + case CONTENTS_EMPTY: + case CONTENTS_SOLID: + case CONTENTS_SKY: //johnfitz -- no blend in sky + cl.cshifts[CSHIFT_CONTENTS] = cshift_empty; + break; + case CONTENTS_LAVA: + cl.cshifts[CSHIFT_CONTENTS] = cshift_lava; + break; + case CONTENTS_SLIME: + cl.cshifts[CSHIFT_CONTENTS] = cshift_slime; + break; + default: + cl.cshifts[CSHIFT_CONTENTS] = cshift_water; + } +} + +/* +============= +V_CalcPowerupCshift +============= +*/ +void V_CalcPowerupCshift (void) +{ + if (cl.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.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.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.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; +} + +/* +============= +V_CalcBlend +============= +*/ +void V_CalcBlend (void) +{ + float r, g, b, a, a2; + int j; + cvar_t *cshiftpercent_cvars[NUM_CSHIFTS] = { + &gl_cshiftpercent_contents, + &gl_cshiftpercent_damage, + &gl_cshiftpercent_bonus, + &gl_cshiftpercent_powerup + }; + + r = 0; + g = 0; + b = 0; + a = 0; + + for (j=0 ; jvalue / 100.0); + // QuakeSpasm + if (!a2) + continue; + a = a + a2*(1-a); + a2 = a2/a; + r = r*(1-a2) + cl.cshifts[j].destcolor[0]*a2; + g = g*(1-a2) + cl.cshifts[j].destcolor[1]*a2; + b = b*(1-a2) + cl.cshifts[j].destcolor[2]*a2; + } + + v_blend[0] = r/255.0; + v_blend[1] = g/255.0; + v_blend[2] = b/255.0; + v_blend[3] = a; + if (v_blend[3] > 1) + v_blend[3] = 1; + if (v_blend[3] < 0) + v_blend[3] = 0; +} + +/* +============= +V_UpdateBlend -- johnfitz -- V_UpdatePalette cleaned up and renamed +============= +*/ +void V_UpdateBlend (void) +{ + int i, j; + qboolean blend_changed; + + V_CalcPowerupCshift (); + + blend_changed = false; + + for (i=0 ; i 180) + a -= 360; + return a; +} + +/* +================== +CalcGunAngle +================== +*/ +void CalcGunAngle (void) +{ + float yaw, pitch, move; + static float oldyaw = 0; + static float oldpitch = 0; + + yaw = r_refdef.viewangles[YAW]; + pitch = -r_refdef.viewangles[PITCH]; + + yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4; + if (yaw > 10) + yaw = 10; + if (yaw < -10) + yaw = -10; + pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4; + if (pitch > 10) + pitch = 10; + if (pitch < -10) + pitch = -10; + move = host_frametime*20; + if (yaw > oldyaw) + { + if (oldyaw + move < yaw) + yaw = oldyaw + move; + } + else + { + if (oldyaw - move > yaw) + yaw = oldyaw - move; + } + + if (pitch > oldpitch) + { + if (oldpitch + move < pitch) + pitch = oldpitch + move; + } + else + { + if (oldpitch - move > pitch) + pitch = oldpitch - move; + } + + oldyaw = yaw; + oldpitch = pitch; + + cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw; + cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch); + + cl.viewent.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + cl.viewent.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + cl.viewent.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + +/* +============== +V_BoundOffsets +============== +*/ +void V_BoundOffsets (void) +{ + entity_t *ent; + + ent = &cl_entities[cl.viewentity]; + +// absolutely bound refresh reletive to entity clipping hull +// so the view can never be inside a solid wall + + if (r_refdef.vieworg[0] < ent->origin[0] - 14) + r_refdef.vieworg[0] = ent->origin[0] - 14; + else if (r_refdef.vieworg[0] > ent->origin[0] + 14) + r_refdef.vieworg[0] = ent->origin[0] + 14; + if (r_refdef.vieworg[1] < ent->origin[1] - 14) + r_refdef.vieworg[1] = ent->origin[1] - 14; + else if (r_refdef.vieworg[1] > ent->origin[1] + 14) + r_refdef.vieworg[1] = ent->origin[1] + 14; + if (r_refdef.vieworg[2] < ent->origin[2] - 22) + r_refdef.vieworg[2] = ent->origin[2] - 22; + else if (r_refdef.vieworg[2] > ent->origin[2] + 30) + r_refdef.vieworg[2] = ent->origin[2] + 30; +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle (void) +{ + r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll (void) +{ + float side; + + side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity); + r_refdef.viewangles[ROLL] += side; + + if (v_dmg_time > 0) + { + r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; + r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; + v_dmg_time -= host_frametime; + } + + if (cl.stats[STAT_HEALTH] <= 0) + { + r_refdef.viewangles[ROLL] = 80; // dead view angle + return; + } +} + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef (void) +{ + entity_t *ent, *view; + float old; + +// ent is the player model (visible when out of body) + ent = &cl_entities[cl.viewentity]; +// view is the weapon model (only visible from inside body) + view = &cl.viewent; + + VectorCopy (ent->origin, r_refdef.vieworg); + VectorCopy (ent->angles, r_refdef.viewangles); + view->model = NULL; + +// allways idle in intermission + old = v_idlescale.value; + v_idlescale.value = 1; + V_AddIdle (); + v_idlescale.value = old; +} + +/* +================== +V_CalcRefdef +================== +*/ +void V_CalcRefdef (void) +{ + entity_t *ent, *view; + int i; + vec3_t forward, right, up; + vec3_t angles; + float bob; + static float oldz = 0; + static vec3_t punch = {0,0,0}; //johnfitz -- v_gunkick + float delta; //johnfitz -- v_gunkick + + V_DriftPitch (); + +// ent is the player model (visible when out of body) + ent = &cl_entities[cl.viewentity]; +// view is the weapon model (only visible from inside body) + view = &cl.viewent; + + +// transform the view offset by the model's matrix to get the offset from +// model origin for the view + ent->angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir + ent->angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir + + bob = V_CalcBob (); + +// refresh position + VectorCopy (ent->origin, r_refdef.vieworg); + r_refdef.vieworg[2] += cl.viewheight + bob; + +// never let it sit exactly on a node line, because a water plane can +// dissapear when viewed with the eye exactly on it. +// the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + r_refdef.vieworg[0] += 1.0/32; + r_refdef.vieworg[1] += 1.0/32; + r_refdef.vieworg[2] += 1.0/32; + + VectorCopy (cl.viewangles, r_refdef.viewangles); + V_CalcViewRoll (); + V_AddIdle (); + +// offsets + angles[PITCH] = -ent->angles[PITCH]; // because entity pitches are actually backward + angles[YAW] = ent->angles[YAW]; + angles[ROLL] = ent->angles[ROLL]; + + AngleVectors (angles, forward, right, up); + + if (cl.maxclients <= 1) //johnfitz -- moved cheat-protection here from V_RenderView + for (i=0 ; i<3 ; i++) + r_refdef.vieworg[i] += scr_ofsx.value*forward[i] + scr_ofsy.value*right[i] + scr_ofsz.value*up[i]; + + V_BoundOffsets (); + +// set up gun position + VectorCopy (cl.viewangles, view->angles); + + CalcGunAngle (); + + VectorCopy (ent->origin, view->origin); + view->origin[2] += cl.viewheight; + + for (i=0 ; i<3 ; i++) + view->origin[i] += forward[i]*bob*0.4; + view->origin[2] += bob; + + //johnfitz -- removed all gun position fudging code (was used to keep gun from getting covered by sbar) + //MarkV -- restored this with r_viewmodel_quake cvar + if (r_viewmodel_quake.value) + { + if (scr_viewsize.value == 110) + view->origin[2] += 1; + else if (scr_viewsize.value == 100) + view->origin[2] += 2; + else if (scr_viewsize.value == 90) + view->origin[2] += 1; + else if (scr_viewsize.value == 80) + view->origin[2] += 0.5; + } + + view->model = cl.model_precache[cl.stats[STAT_WEAPON]]; + view->frame = cl.stats[STAT_WEAPONFRAME]; + view->colormap = vid.colormap; + +//johnfitz -- v_gunkick + if (v_gunkick.value == 1) //original quake kick + VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles); + if (v_gunkick.value == 2) //lerped kick + { + for (i=0; i<3; i++) + if (punch[i] != v_punchangles[0][i]) + { + //speed determined by how far we need to lerp in 1/10th of a second + delta = (v_punchangles[0][i]-v_punchangles[1][i]) * host_frametime * 10; + + if (delta > 0) + punch[i] = q_min(punch[i]+delta, v_punchangles[0][i]); + else if (delta < 0) + punch[i] = q_max(punch[i]+delta, v_punchangles[0][i]); + } + + VectorAdd (r_refdef.viewangles, punch, r_refdef.viewangles); + } +//johnfitz + +// smooth out stair step ups + if (!noclip_anglehack && cl.onground && ent->origin[2] - oldz > 0) //johnfitz -- added exception for noclip + //FIXME: noclip_anglehack is set on the server, so in a nonlocal game this won't work. + { + float steptime; + + steptime = cl.time - cl.oldtime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 80; + if (oldz > ent->origin[2]) + oldz = ent->origin[2]; + if (ent->origin[2] - oldz > 12) + oldz = ent->origin[2] - 12; + r_refdef.vieworg[2] += oldz - ent->origin[2]; + view->origin[2] += oldz - ent->origin[2]; + } + else + oldz = ent->origin[2]; + + if (chase_active.value) + Chase_UpdateForDrawing (); //johnfitz +} + +/* +================== +V_RenderView + +The player's clipping box goes from (-16 -16 -24) to (16 16 32) from +the entity origin, so any view position inside that will be valid +================== +*/ +extern vrect_t scr_vrect; + +void V_RenderView (void) +{ + if (con_forcedup) + return; + + if (cl.intermission) + V_CalcIntermissionRefdef (); + else if (!cl.paused /* && (cl.maxclients > 1 || key_dest == key_game) */) + V_CalcRefdef (); + + //johnfitz -- removed lcd code + + R_RenderView (); + + V_PolyBlend (); //johnfitz -- moved here from R_Renderview (); +} + +/* +============================================================================== + + INIT + +============================================================================== +*/ + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + Cmd_AddCommand ("v_cshift", V_cshift_f); + Cmd_AddCommand ("bf", V_BonusFlash_f); + Cmd_AddCommand ("centerview", V_StartPitchDrift); + + 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 (&gl_cshiftpercent); + Cvar_RegisterVariable (&gl_cshiftpercent_contents); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_damage); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_bonus); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_powerup); // QuakeSpasm + + Cvar_RegisterVariable (&scr_ofsx); + Cvar_RegisterVariable (&scr_ofsy); + Cvar_RegisterVariable (&scr_ofsz); + Cvar_RegisterVariable (&cl_rollspeed); + Cvar_RegisterVariable (&cl_rollangle); + Cvar_RegisterVariable (&cl_bob); + Cvar_RegisterVariable (&cl_bobcycle); + Cvar_RegisterVariable (&cl_bobup); + + Cvar_RegisterVariable (&v_kicktime); + Cvar_RegisterVariable (&v_kickroll); + Cvar_RegisterVariable (&v_kickpitch); + Cvar_RegisterVariable (&v_gunkick); //johnfitz + + Cvar_RegisterVariable (&r_viewmodel_quake); //MarkV +} + diff --git a/source/view.h b/source/view.h new file mode 100644 index 0000000..af0cdf1 --- /dev/null +++ b/source/view.h @@ -0,0 +1,39 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_VIEW_H +#define _QUAKE_VIEW_H + +extern cvar_t vid_gamma; +extern cvar_t vid_contrast; + +extern float v_blend[4]; + +void V_Init (void); +void V_RenderView (void); +void V_CalcBlend (void); +void V_UpdateBlend (void); +float V_CalcRoll (vec3_t angles, vec3_t velocity); +//void V_UpdatePalette (void); //johnfitz + +#endif /* _QUAKE_VIEW_H */ + diff --git a/source/wad.c b/source/wad.c new file mode 100644 index 0000000..aae5f38 --- /dev/null +++ b/source/wad.c @@ -0,0 +1,167 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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.c + +#include "quakedef.h" + +int wad_numlumps; +lumpinfo_t *wad_lumps; +byte *wad_base = NULL; + +void SwapPic (qpic_t *pic); + +/* +================== +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. +================== +*/ +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; +} + +/* +==================== +W_LoadWadFile +==================== +*/ +void W_LoadWadFile (void) //johnfitz -- filename is now hard-coded for honesty +{ + lumpinfo_t *lump_p; + wadinfo_t *header; + int i; + int infotableofs; + const char *filename = WADFILENAME; + + //johnfitz -- modified to use malloc + //TODO: use cache_alloc + if (wad_base) + free (wad_base); + wad_base = COM_LoadMallocFile (filename, NULL); + if (!wad_base) + Sys_Error ("W_LoadWadFile: couldn't load %s\n\n" + "Basedir is: %s\n\n" + "Check that this has an " GAMENAME " subdirectory containing pak0.pak and pak1.pak, " + "or use the -basedir command-line option to specify another directory.", + filename, com_basedir); + + header = (wadinfo_t *)wad_base; + + if (header->identification[0] != 'W' || header->identification[1] != 'A' + || header->identification[2] != 'D' || header->identification[3] != '2') + Sys_Error ("Wad file %s doesn't have WAD2 id\n",filename); + + wad_numlumps = LittleLong(header->numlumps); + infotableofs = LittleLong(header->infotableofs); + wad_lumps = (lumpinfo_t *)(wad_base + infotableofs); + + for (i=0, lump_p = wad_lumps ; ifilepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + W_CleanupName (lump_p->name, lump_p->name); // CAUTION: in-place editing!!! + if (lump_p->type == TYP_QPIC) + SwapPic ( (qpic_t *)(wad_base + lump_p->filepos)); + } +} + + +/* +============= +W_GetLumpinfo +============= +*/ +lumpinfo_t *W_GetLumpinfo (const char *name) +{ + int i; + lumpinfo_t *lump_p; + char clean[16]; + + W_CleanupName (name, clean); + + for (lump_p=wad_lumps, i=0 ; iname)) + return lump_p; + } + + Con_SafePrintf ("W_GetLumpinfo: %s not found\n", name); //johnfitz -- was Sys_Error + return NULL; +} + +void *W_GetLumpName (const char *name) +{ + lumpinfo_t *lump; + + lump = W_GetLumpinfo (name); + + if (!lump) return NULL; //johnfitz + + return (void *)(wad_base + lump->filepos); +} + +void *W_GetLumpNum (int num) +{ + lumpinfo_t *lump; + + if (num < 0 || num > wad_numlumps) + Sys_Error ("W_GetLumpNum: bad number: %i", num); + + lump = wad_lumps + num; + + return (void *)(wad_base + lump->filepos); +} + +/* +============================================================================= + +automatic byte swapping + +============================================================================= +*/ + +void SwapPic (qpic_t *pic) +{ + pic->width = LittleLong(pic->width); + pic->height = LittleLong(pic->height); +} diff --git a/source/wad.h b/source/wad.h new file mode 100644 index 0000000..8838e0e --- /dev/null +++ b/source/wad.h @@ -0,0 +1,82 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_WAD_H +#define _QUAKE_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 + +#define WADFILENAME "gfx.wad" //johnfitz -- filename is now hard-coded for honesty + +typedef struct +{ + int width, height; + byte data[4]; // variably sized +} qpic_t; + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +extern int wad_numlumps; +extern lumpinfo_t *wad_lumps; +extern byte *wad_base; + +void W_LoadWadFile (void); //johnfitz -- filename is now hard-coded for honesty +void W_CleanupName (const char *in, char *out); +lumpinfo_t *W_GetLumpinfo (const char *name); +void *W_GetLumpName (const char *name); +void *W_GetLumpNum (int num); + +void SwapPic (qpic_t *pic); + +#endif /* _QUAKE_WAD_H */ + diff --git a/source/world.c b/source/world.c new file mode 100644 index 0000000..475cef6 --- /dev/null +++ b/source/world.c @@ -0,0 +1,937 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +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" + +/* + +entities never clip against themselves, or their owner + +line of sight checks trace->crosscontent, but bullets don't + +*/ + + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; +} moveclip_t; + + +int SV_HullPointContents (hull_t *hull, int num, vec3_t p); + +/* +=============================================================================== + +HULL BOXES + +=============================================================================== +*/ + + +static hull_t box_hull; +static mclipnode_t box_clipnodes[6]; //johnfitz -- was dclipnode_t +static mplane_t box_planes[6]; + +/* +=================== +SV_InitBoxHull + +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. +=================== +*/ +void SV_InitBoxHull (void) +{ + int i; + int side; + + 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; + } + +} + + +/* +=================== +SV_HullForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +hull_t *SV_HullForBox (vec3_t mins, vec3_t maxs) +{ + box_planes[0].dist = maxs[0]; + box_planes[1].dist = mins[0]; + box_planes[2].dist = maxs[1]; + box_planes[3].dist = mins[1]; + box_planes[4].dist = maxs[2]; + box_planes[5].dist = mins[2]; + + return &box_hull; +} + + + +/* +================ +SV_HullForEntity + +Returns a hull that can be used for testing or clipping an object of mins/maxs +size. +Offset is filled in to contain the adjustment that must be added to the +testing object's origin to get a point to use with the returned hull. +================ +*/ +hull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset) +{ + qmodel_t *model; + vec3_t size; + vec3_t hullmins, hullmaxs; + hull_t *hull; + +// decide which clipping hull to use, based on the size + if (ent->v.solid == SOLID_BSP) + { // explicit hulls in the BSP model + if (ent->v.movetype != MOVETYPE_PUSH) + Host_Error ("SOLID_BSP without MOVETYPE_PUSH (%s at %f %f %f)", + PR_GetString(ent->v.classname), ent->v.origin[0], ent->v.origin[1], ent->v.origin[2]); + + model = sv.models[ (int)ent->v.modelindex ]; + + if (!model || model->type != mod_brush) + Host_Error ("SOLID_BSP with a non bsp model (%s at %f %f %f)", + PR_GetString(ent->v.classname), ent->v.origin[0], ent->v.origin[1], ent->v.origin[2]); + + VectorSubtract (maxs, mins, size); + if (size[0] < 3) + hull = &model->hulls[0]; + else if (size[0] <= 32) + hull = &model->hulls[1]; + else + hull = &model->hulls[2]; + +// calculate an offset value to center the origin + VectorSubtract (hull->clip_mins, mins, offset); + VectorAdd (offset, ent->v.origin, offset); + } + else + { // create a temp hull from bounding box sizes + + VectorSubtract (ent->v.mins, maxs, hullmins); + VectorSubtract (ent->v.maxs, mins, hullmaxs); + hull = SV_HullForBox (hullmins, hullmaxs); + + VectorCopy (ent->v.origin, offset); + } + + + return hull; +} + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t trigger_edicts; + link_t solid_edicts; +} areanode_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 32 + +static areanode_t sv_areanodes[AREA_NODES]; +static int sv_numareanodes; + +/* +=============== +SV_CreateAreaNode + +=============== +*/ +areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs) +{ + areanode_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_areanodes[sv_numareanodes]; + sv_numareanodes++; + + ClearLink (&anode->trigger_edicts); + ClearLink (&anode->solid_edicts); + + if (depth == AREA_DEPTH) + { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract (maxs, mins, size); + if (size[0] > size[1]) + anode->axis = 0; + else + anode->axis = 1; + + anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); + VectorCopy (mins, mins1); + VectorCopy (mins, mins2); + VectorCopy (maxs, maxs1); + VectorCopy (maxs, maxs2); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + + anode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld (void) +{ + SV_InitBoxHull (); + + memset (sv_areanodes, 0, sizeof(sv_areanodes)); + sv_numareanodes = 0; + SV_CreateAreaNode (0, sv.worldmodel->mins, sv.worldmodel->maxs); +} + + +/* +=============== +SV_UnlinkEdict + +=============== +*/ +void SV_UnlinkEdict (edict_t *ent) +{ + if (!ent->area.prev) + return; // not linked in anywhere + RemoveLink (&ent->area); + ent->area.prev = ent->area.next = NULL; +} + + +/* +==================== +SV_AreaTriggerEdicts + +Spike -- just builds a list of entities within the area, rather than walking +them and risking the list getting corrupt. +==================== +*/ +static void +SV_AreaTriggerEdicts ( edict_t *ent, areanode_t *node, edict_t **list, int *listcount, const int listspace ) +{ + link_t *l, *next; + edict_t *touch; + +// touch linked edicts + for (l = node->trigger_edicts.next ; l != &node->trigger_edicts ; l = next) + { + next = l->next; + touch = EDICT_FROM_AREA(l); + if (touch == ent) + continue; + if (!touch->v.touch || touch->v.solid != SOLID_TRIGGER) + continue; + if (ent->v.absmin[0] > touch->v.absmax[0] + || ent->v.absmin[1] > touch->v.absmax[1] + || ent->v.absmin[2] > touch->v.absmax[2] + || ent->v.absmax[0] < touch->v.absmin[0] + || ent->v.absmax[1] < touch->v.absmin[1] + || ent->v.absmax[2] < touch->v.absmin[2] ) + continue; + + if (*listcount == listspace) + return; // should never happen + + list[*listcount] = touch; + (*listcount)++; + } + +// recurse down both sides + if (node->axis == -1) + return; + + if ( ent->v.absmax[node->axis] > node->dist ) + SV_AreaTriggerEdicts ( ent, node->children[0], list, listcount, listspace ); + if ( ent->v.absmin[node->axis] < node->dist ) + SV_AreaTriggerEdicts ( ent, node->children[1], list, listcount, listspace ); +} + +/* +==================== +SV_TouchLinks + +ericw -- copy the touching edicts to an array (on the hunk) so we can avoid +iteating the trigger_edicts linked list while calling PR_ExecuteProgram +which could potentially corrupt the list while it's being iterated. +Based on code from Spike. +==================== +*/ +void SV_TouchLinks (edict_t *ent) +{ + edict_t **list; + edict_t *touch; + int old_self, old_other; + int i, listcount; + int mark; + + mark = Hunk_LowMark (); + list = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); + + listcount = 0; + SV_AreaTriggerEdicts (ent, sv_areanodes, list, &listcount, sv.num_edicts); + + for (i = 0; i < listcount; i++) + { + touch = list[i]; + // re-validate in case of PR_ExecuteProgram having side effects that make + // edicts later in the list no longer touch + if (touch == ent) + continue; + if (!touch->v.touch || touch->v.solid != SOLID_TRIGGER) + continue; + if (ent->v.absmin[0] > touch->v.absmax[0] + || ent->v.absmin[1] > touch->v.absmax[1] + || ent->v.absmin[2] > touch->v.absmax[2] + || ent->v.absmax[0] < touch->v.absmin[0] + || ent->v.absmax[1] < touch->v.absmin[1] + || ent->v.absmax[2] < touch->v.absmin[2] ) + continue; + old_self = pr_global_struct->self; + old_other = pr_global_struct->other; + + pr_global_struct->self = EDICT_TO_PROG(touch); + pr_global_struct->other = EDICT_TO_PROG(ent); + pr_global_struct->time = sv.time; + PR_ExecuteProgram (touch->v.touch); + + pr_global_struct->self = old_self; + pr_global_struct->other = old_other; + } + +// free hunk-allocated edicts array + Hunk_FreeToLowMark (mark); +} + + +/* +=============== +SV_FindTouchedLeafs + +=============== +*/ +void SV_FindTouchedLeafs (edict_t *ent, mnode_t *node) +{ + mplane_t *splitplane; + mleaf_t *leaf; + int sides; + int leafnum; + + if (node->contents == CONTENTS_SOLID) + return; + +// add an efrag if the node is a leaf + + if ( node->contents < 0) + { + if (ent->num_leafs == MAX_ENT_LEAFS) + return; + + leaf = (mleaf_t *)node; + leafnum = leaf - sv.worldmodel->leafs - 1; + + ent->leafnums[ent->num_leafs] = leafnum; + ent->num_leafs++; + return; + } + +// NODE_MIXED + + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE(ent->v.absmin, ent->v.absmax, splitplane); + +// recurse down the contacted sides + if (sides & 1) + SV_FindTouchedLeafs (ent, node->children[0]); + + if (sides & 2) + SV_FindTouchedLeafs (ent, node->children[1]); +} + +/* +=============== +SV_LinkEdict + +=============== +*/ +void SV_LinkEdict (edict_t *ent, qboolean touch_triggers) +{ + areanode_t *node; + + if (ent->area.prev) + SV_UnlinkEdict (ent); // unlink from old position + + if (ent == sv.edicts) + return; // don't add the world + + if (ent->free) + return; + +// set the abs box + VectorAdd (ent->v.origin, ent->v.mins, ent->v.absmin); + VectorAdd (ent->v.origin, ent->v.maxs, ent->v.absmax); + +// +// to make items easier to pick up and allow them to be grabbed off +// of shelves, the abs sizes are expanded +// + if ((int)ent->v.flags & FL_ITEM) + { + ent->v.absmin[0] -= 15; + ent->v.absmin[1] -= 15; + ent->v.absmax[0] += 15; + ent->v.absmax[1] += 15; + } + else + { // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + ent->v.absmin[0] -= 1; + ent->v.absmin[1] -= 1; + ent->v.absmin[2] -= 1; + ent->v.absmax[0] += 1; + ent->v.absmax[1] += 1; + ent->v.absmax[2] += 1; + } + +// link to PVS leafs + ent->num_leafs = 0; + if (ent->v.modelindex) + SV_FindTouchedLeafs (ent, sv.worldmodel->nodes); + + if (ent->v.solid == SOLID_NOT) + return; + +// find the first node that the ent's box crosses + node = sv_areanodes; + while (1) + { + if (node->axis == -1) + break; + if (ent->v.absmin[node->axis] > node->dist) + node = node->children[0]; + else if (ent->v.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + +// link it in + + if (ent->v.solid == SOLID_TRIGGER) + InsertLinkBefore (&ent->area, &node->trigger_edicts); + else + InsertLinkBefore (&ent->area, &node->solid_edicts); + +// if touch_triggers, touch all entities at this node and decend for more + if (touch_triggers) + SV_TouchLinks ( ent ); +} + + + +/* +=============================================================================== + +POINT TESTING IN HULLS + +=============================================================================== +*/ + +/* +================== +SV_HullPointContents + +================== +*/ +int SV_HullPointContents (hull_t *hull, int num, vec3_t p) +{ + float d; + mclipnode_t *node; //johnfitz -- was dclipnode_t + mplane_t *plane; + + while (num >= 0) + { + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_HullPointContents: bad node number"); + + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DoublePrecisionDotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return num; +} + + +/* +================== +SV_PointContents + +================== +*/ +int SV_PointContents (vec3_t p) +{ + int cont; + + cont = SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); + if (cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN) + cont = CONTENTS_WATER; + return cont; +} + +int SV_TruePointContents (vec3_t p) +{ + return SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); +} + +//=========================================================================== + +/* +============ +SV_TestEntityPosition + +This could be a lot more efficient... +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, 0, ent); + + if (trace.startsolid) + return sv.edicts; + + return NULL; +} + + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ + +/* +================== +SV_RecursiveHullCheck + +================== +*/ +qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace) +{ + mclipnode_t *node; //johnfitz -- was dclipnode_t + mplane_t *plane; + float t1, t2; + float frac; + int i; + vec3_t mid; + int side; + float midf; + +// check for empty + if (num < 0) + { + if (num != CONTENTS_SOLID) + { + trace->allsolid = false; + if (num == CONTENTS_EMPTY) + trace->inopen = true; + else + trace->inwater = true; + } + else + trace->startsolid = true; + return true; // empty + } + + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_RecursiveHullCheck: bad node number"); + +// +// find the point distances +// + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DoublePrecisionDotProduct (plane->normal, p1) - plane->dist; + t2 = DoublePrecisionDotProduct (plane->normal, p2) - plane->dist; + } + +#if 1 + if (t1 >= 0 && t2 >= 0) + return SV_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace); + if (t1 < 0 && t2 < 0) + return SV_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace); +#else + if ( (t1 >= DIST_EPSILON && t2 >= DIST_EPSILON) || (t2 > t1 && t1 >= 0) ) + return SV_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace); + if ( (t1 <= -DIST_EPSILON && t2 <= -DIST_EPSILON) || (t2 < t1 && t1 <= 0) ) + return SV_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace); +#endif + +// put the crosspoint DIST_EPSILON pixels on the near side + if (t1 < 0) + frac = (t1 + DIST_EPSILON)/(t1-t2); + else + frac = (t1 - DIST_EPSILON)/(t1-t2); + if (frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + + side = (t1 < 0); + +// move up to the node + if (!SV_RecursiveHullCheck (hull, node->children[side], p1f, midf, p1, mid, trace) ) + return false; + +#ifdef PARANOID + if (SV_HullPointContents (sv_hullmodel, mid, node->children[side]) + == CONTENTS_SOLID) + { + Con_Printf ("mid PointInHullSolid\n"); + return false; + } +#endif + + if (SV_HullPointContents (hull, node->children[side^1], mid) + != CONTENTS_SOLID) +// go past the node + return SV_RecursiveHullCheck (hull, node->children[side^1], midf, p2f, mid, p2, trace); + + if (trace->allsolid) + return false; // never got out of the solid area + +//================== +// the other side of the node is solid, this is the impact point +//================== + if (!side) + { + VectorCopy (plane->normal, trace->plane.normal); + trace->plane.dist = plane->dist; + } + else + { + VectorSubtract (vec3_origin, plane->normal, trace->plane.normal); + trace->plane.dist = -plane->dist; + } + + while (SV_HullPointContents (hull, hull->firstclipnode, mid) + == CONTENTS_SOLID) + { // shouldn't really happen, but does occasionally + frac -= 0.1; + if (frac < 0) + { + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + Con_DPrintf ("backup past 0\n"); + return false; + } + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + } + + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + + return false; +} + + +/* +================== +SV_ClipMoveToEntity + +Handles selection or creation of a clipping hull, and offseting (and +eventually rotation) of the end points +================== +*/ +trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + trace_t trace; + vec3_t offset; + vec3_t start_l, end_l; + hull_t *hull; + +// fill in a default trace + memset (&trace, 0, sizeof(trace_t)); + trace.fraction = 1; + trace.allsolid = true; + VectorCopy (end, trace.endpos); + +// get the clipping hull + hull = SV_HullForEntity (ent, mins, maxs, offset); + + VectorSubtract (start, offset, start_l); + VectorSubtract (end, offset, end_l); + +// trace a line through the apropriate clipping hull + SV_RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace); + +// fix trace up by the offset + if (trace.fraction != 1) + VectorAdd (trace.endpos, offset, trace.endpos); + +// did we clip the move? + if (trace.fraction < 1 || trace.startsolid ) + trace.ent = ent; + + return trace; +} + +//=========================================================================== + +/* +==================== +SV_ClipToLinks + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +void SV_ClipToLinks ( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + edict_t *touch; + trace_t trace; + +// touch linked edicts + for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next) + { + next = l->next; + touch = EDICT_FROM_AREA(l); + if (touch->v.solid == SOLID_NOT) + continue; + if (touch == clip->passedict) + continue; + if (touch->v.solid == SOLID_TRIGGER) + Sys_Error ("Trigger in clipping list"); + + if (clip->type == MOVE_NOMONSTERS && touch->v.solid != SOLID_BSP) + continue; + + if (clip->boxmins[0] > touch->v.absmax[0] + || clip->boxmins[1] > touch->v.absmax[1] + || clip->boxmins[2] > touch->v.absmax[2] + || clip->boxmaxs[0] < touch->v.absmin[0] + || clip->boxmaxs[1] < touch->v.absmin[1] + || clip->boxmaxs[2] < touch->v.absmin[2] ) + continue; + + if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0]) + continue; // points never interact + + // might intersect, so do an exact clip + if (clip->trace.allsolid) + return; + if (clip->passedict) + { + if (PROG_TO_EDICT(touch->v.owner) == clip->passedict) + continue; // don't clip against own missiles + if (PROG_TO_EDICT(clip->passedict->v.owner) == touch) + continue; // don't clip against owner + } + + if ((int)touch->v.flags & FL_MONSTER) + trace = SV_ClipMoveToEntity (touch, clip->start, clip->mins2, clip->maxs2, clip->end); + else + trace = SV_ClipMoveToEntity (touch, clip->start, clip->mins, clip->maxs, clip->end); + if (trace.allsolid || trace.startsolid || + trace.fraction < clip->trace.fraction) + { + trace.ent = touch; + if (clip->trace.startsolid) + { + clip->trace = trace; + clip->trace.startsolid = true; + } + else + clip->trace = trace; + } + else if (trace.startsolid) + clip->trace.startsolid = true; + } + +// recurse down both sides + if (node->axis == -1) + return; + + if ( clip->boxmaxs[node->axis] > node->dist ) + SV_ClipToLinks ( node->children[0], clip ); + if ( clip->boxmins[node->axis] < node->dist ) + SV_ClipToLinks ( node->children[1], clip ); +} + + +/* +================== +SV_MoveBounds +================== +*/ +void SV_MoveBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs) +{ +#if 0 +// debug to test against everything +boxmins[0] = boxmins[1] = boxmins[2] = -9999; +boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999; +#else + int i; + + for (i=0 ; i<3 ; i++) + { + if (end[i] > start[i]) + { + boxmins[i] = start[i] + mins[i] - 1; + boxmaxs[i] = end[i] + maxs[i] + 1; + } + else + { + boxmins[i] = end[i] + mins[i] - 1; + boxmaxs[i] = start[i] + maxs[i] + 1; + } + } +#endif +} + +/* +================== +SV_Move +================== +*/ +trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict) +{ + moveclip_t clip; + int i; + + memset ( &clip, 0, sizeof ( moveclip_t ) ); + +// clip to world + clip.trace = SV_ClipMoveToEntity ( sv.edicts, start, mins, maxs, end ); + + clip.start = start; + clip.end = end; + clip.mins = mins; + clip.maxs = maxs; + clip.type = type; + clip.passedict = passedict; + + if (type == MOVE_MISSILE) + { + for (i=0 ; i<3 ; i++) + { + clip.mins2[i] = -15; + clip.maxs2[i] = 15; + } + } + else + { + VectorCopy (mins, clip.mins2); + VectorCopy (maxs, clip.maxs2); + } + +// create the bounding box of the entire move + SV_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs ); + +// clip to entities + SV_ClipToLinks ( sv_areanodes, &clip ); + + return clip.trace; +} + diff --git a/source/world.h b/source/world.h new file mode 100644 index 0000000..fed90b0 --- /dev/null +++ b/source/world.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 _QUAKE_WORLD_H +#define _QUAKE_WORLD_H + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on +} trace_t; + + +#define MOVE_NORMAL 0 +#define MOVE_NOMONSTERS 1 +#define MOVE_MISSILE 2 + + +void SV_ClearWorld (void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEdict (edict_t *ent); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself +// flags ent->v.modified + +void SV_LinkEdict (edict_t *ent, qboolean touch_triggers); +// Needs to be called any time an entity changes origin, mins, maxs, or solid +// flags ent->v.modified +// sets ent->v.absmin and ent->v.absmax +// if touchtriggers, calls prog functions for the intersected triggers + +int SV_PointContents (vec3_t p); +int SV_TruePointContents (vec3_t p); +// returns the CONTENTS_* value from the world at the given point. +// does not check any entities at all +// the non-true version remaps the water current contents to content_water + +edict_t *SV_TestEntityPosition (edict_t *ent); + +trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict); +// mins and maxs are reletive + +// if the entire move stays in a solid volume, trace.allsolid will be set + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// nomonsters is used for line of sight or edge testing, where mosnters +// shouldn't be considered solid objects + +// passedict is explicitly excluded from clipping checks (normally NULL) + +qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace); + +#endif /* _QUAKE_WORLD_H */ + diff --git a/source/wsaerror.h b/source/wsaerror.h new file mode 100644 index 0000000..b41a98e --- /dev/null +++ b/source/wsaerror.h @@ -0,0 +1,91 @@ + +/* strings for winsock error codes. + * from online references, such as + * http://aluigi.org/mytoolz/winerr.h + * http://www.winsock-error.com or + * http://www.sockets.com/err_lst1.htm + */ + +#ifndef __wsaerr_static +#define __wsaerr_static static +#endif /* static */ + +__wsaerr_static const char *__WSAE_StrError (int err) +{ + switch (err) + { + case 0: return "No error"; + case WSAEINTR: return "Interrupted system call"; /* 10004 */ + case WSAEBADF: return "Bad file number"; /* 10009 */ + case WSAEACCES: return "Permission denied"; /* 10013 */ + case WSAEFAULT: return "Bad address"; /* 10014 */ + case WSAEINVAL: return "Invalid argument (not bind)"; /* 10022 */ + case WSAEMFILE: return "Too many open files"; /* 10024 */ + case WSAEWOULDBLOCK: return "Operation would block"; /* 10035 */ + case WSAEINPROGRESS: return "Operation now in progress"; /* 10036 */ + case WSAEALREADY: return "Operation already in progress"; /* 10037 */ + case WSAENOTSOCK: return "Socket operation on non-socket"; /* 10038 */ + case WSAEDESTADDRREQ: return "Destination address required"; /* 10039 */ + case WSAEMSGSIZE: return "Message too long"; /* 10040 */ + case WSAEPROTOTYPE: return "Protocol wrong type for socket"; /* 10041 */ + case WSAENOPROTOOPT: return "Bad protocol option"; /* 10042 */ + case WSAEPROTONOSUPPORT: return "Protocol not supported"; /* 10043 */ + case WSAESOCKTNOSUPPORT: return "Socket type not supported"; /* 10044 */ + case WSAEOPNOTSUPP: return "Operation not supported on socket"; /* 10045 */ + case WSAEPFNOSUPPORT: return "Protocol family not supported"; /* 10046 */ + case WSAEAFNOSUPPORT: return "Address family not supported by protocol family"; /* 10047 */ + case WSAEADDRINUSE: return "Address already in use"; /* 10048 */ + case WSAEADDRNOTAVAIL: return "Can't assign requested address"; /* 10049 */ + case WSAENETDOWN: return "Network is down"; /* 10050 */ + case WSAENETUNREACH: return "Network is unreachable"; /* 10051 */ + case WSAENETRESET: return "Net dropped connection or reset"; /* 10052 */ + case WSAECONNABORTED: return "Software caused connection abort"; /* 10053 */ + case WSAECONNRESET: return "Connection reset by peer"; /* 10054 */ + case WSAENOBUFS: return "No buffer space available"; /* 10055 */ + case WSAEISCONN: return "Socket is already connected"; /* 10056 */ + case WSAENOTCONN: return "Socket is not connected"; /* 10057 */ + case WSAESHUTDOWN: return "Can't send after socket shutdown"; /* 10058 */ + case WSAETOOMANYREFS: return "Too many references, can't splice"; /* 10059 */ + case WSAETIMEDOUT: return "Connection timed out"; /* 10060 */ + case WSAECONNREFUSED: return "Connection refused"; /* 10061 */ + case WSAELOOP: return "Too many levels of symbolic links"; /* 10062 */ + case WSAENAMETOOLONG: return "File name too long"; /* 10063 */ + case WSAEHOSTDOWN: return "Host is down"; /* 10064 */ + case WSAEHOSTUNREACH: return "No Route to Host"; /* 10065 */ + case WSAENOTEMPTY: return "Directory not empty"; /* 10066 */ + case WSAEPROCLIM: return "Too many processes"; /* 10067 */ + case WSAEUSERS: return "Too many users"; /* 10068 */ + case WSAEDQUOT: return "Disc Quota Exceeded"; /* 10069 */ + case WSAESTALE: return "Stale NFS file handle"; /* 10070 */ + case WSAEREMOTE: return "Too many levels of remote in path"; /* 10071 */ + case WSAEDISCON: return "Graceful shutdown in progress"; /* 10101 */ + + case WSASYSNOTREADY: return "Network SubSystem is unavailable"; /* 10091 */ + case WSAVERNOTSUPPORTED: return "WINSOCK DLL Version out of range"; /* 10092 */ + case WSANOTINITIALISED: return "Successful WSASTARTUP not yet performed"; /* 10093 */ + case WSAHOST_NOT_FOUND: return "Authoritative answer: Host not found"; /* 11001 */ + case WSATRY_AGAIN: return "Non-Authoritative: Host not found or SERVERFAIL"; /* 11002 */ + case WSANO_RECOVERY: return "Non-Recoverable errors, FORMERR, REFUSED, NOTIMP"; /* 11003 */ + case WSANO_DATA: return "Valid name, no data record of requested type"; /* 11004 */ + + case WSAENOMORE: return "10102: No more results"; /* 10102 */ + case WSAECANCELLED: return "10103: Call has been canceled"; /* 10103 */ + case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid"; /* 10104 */ + case WSAEINVALIDPROVIDER: return "Service provider is invalid"; /* 10105 */ + case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize"; /* 10106 */ + case WSASYSCALLFAILURE: return "System call failure"; /* 10107 */ + case WSASERVICE_NOT_FOUND: return "Service not found"; /* 10108 */ + case WSATYPE_NOT_FOUND: return "Class type not found"; /* 10109 */ + case WSA_E_NO_MORE: return "10110: No more results"; /* 10110 */ + case WSA_E_CANCELLED: return "10111: Call was canceled"; /* 10111 */ + case WSAEREFUSED: return "Database query was refused"; /* 10112 */ + + default: + { + static char _err_unknown[64]; + sprintf(_err_unknown, "Unknown WSAE error (%d)", err); + return _err_unknown; + } + } +} + diff --git a/source/zone.c b/source/zone.c new file mode 100644 index 0000000..05936bb --- /dev/null +++ b/source/zone.c @@ -0,0 +1,989 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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. + +*/ +// zone.c + +#include "quakedef.h" + +#define DYNAMIC_SIZE (4 * 1024 * 1024) // ericw -- was 512KB (64-bit) / 384KB (32-bit) + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct memblock_s +{ + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + int id; // should be ZONEID + int pad; // pad to 64 bit boundary + struct memblock_s *next, *prev; +} memblock_t; + +typedef struct +{ + int size; // total bytes malloced, including header + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +void Cache_FreeLow (int new_low_hunk); +void Cache_FreeHigh (int new_high_hunk); + + +/* +============================================================================== + + ZONE MEMORY ALLOCATION + +There is never any space between memblocks, and there will never be two +contiguous free memblocks. + +The rover can be left pointing at a non-empty block + +The zone calls are pretty much only used for small strings and structures, +all big things are allocated on the hunk. +============================================================================== +*/ + +static memzone_t *mainzone; + + +/* +======================== +Z_Free +======================== +*/ +void Z_Free (void *ptr) +{ + memblock_t *block, *other; + + if (!ptr) + Sys_Error ("Z_Free: NULL pointer"); + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block->id != ZONEID) + Sys_Error ("Z_Free: freed a pointer without ZONEID"); + if (block->tag == 0) + Sys_Error ("Z_Free: freed a freed pointer"); + + block->tag = 0; // mark as free + + other = block->prev; + if (!other->tag) + { // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + if (block == mainzone->rover) + mainzone->rover = other; + block = other; + } + + other = block->next; + if (!other->tag) + { // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + if (other == mainzone->rover) + mainzone->rover = block; + } +} + + +static void *Z_TagMalloc (int size, int tag) +{ + int extra; + memblock_t *start, *rover, *newblock, *base; + + if (!tag) + Sys_Error ("Z_TagMalloc: tried to use a 0 tag"); + +// +// scan through the block list looking for the first free block +// of sufficient size +// + size += sizeof(memblock_t); // account for size of block header + size += 4; // space for memory trash tester + size = (size + 7) & ~7; // align to 8-byte boundary + + base = rover = mainzone->rover; + start = base->prev; + + do + { + if (rover == start) // scaned all the way around the list + return NULL; + if (rover->tag) + base = rover = rover->next; + else + rover = rover->next; + } while (base->tag || base->size < size); + +// +// found a block big enough +// + extra = base->size - size; + if (extra > MINFRAGMENT) + { // there will be a free fragment after the allocated block + newblock = (memblock_t *) ((byte *)base + size ); + newblock->size = extra; + newblock->tag = 0; // free block + newblock->prev = base; + newblock->id = ZONEID; + newblock->next = base->next; + newblock->next->prev = newblock; + base->next = newblock; + base->size = size; + } + + base->tag = tag; // no longer a free block + + mainzone->rover = base->next; // next allocation will start looking here + + base->id = ZONEID; + +// marker for memory trash testing + *(int *)((byte *)base + base->size - 4) = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_CheckHeap +======================== +*/ +static void Z_CheckHeap (void) +{ + memblock_t *block; + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &mainzone->blocklist) + break; // all blocks have been hit + if ( (byte *)block + block->size != (byte *)block->next) + Sys_Error ("Z_CheckHeap: block size does not touch the next block\n"); + if ( block->next->prev != block) + Sys_Error ("Z_CheckHeap: next block doesn't have proper back link\n"); + if (!block->tag && !block->next->tag) + Sys_Error ("Z_CheckHeap: two consecutive free blocks\n"); + } +} + + +/* +======================== +Z_Malloc +======================== +*/ +void *Z_Malloc (int size) +{ + void *buf; + + Z_CheckHeap (); // DEBUG + buf = Z_TagMalloc (size, 1); + if (!buf) + Sys_Error ("Z_Malloc: failed on allocation of %i bytes",size); + Q_memset (buf, 0, size); + + return buf; +} + +/* +======================== +Z_Realloc +======================== +*/ +void *Z_Realloc(void *ptr, int size) +{ + int old_size; + void *old_ptr; + memblock_t *block; + + if (!ptr) + return Z_Malloc (size); + + block = (memblock_t *) ((byte *) ptr - sizeof (memblock_t)); + if (block->id != ZONEID) + Sys_Error ("Z_Realloc: realloced a pointer without ZONEID"); + if (block->tag == 0) + Sys_Error ("Z_Realloc: realloced a freed pointer"); + + old_size = block->size; + old_size -= (4 + (int)sizeof(memblock_t)); /* see Z_TagMalloc() */ + old_ptr = ptr; + + Z_Free (ptr); + ptr = Z_TagMalloc (size, 1); + if (!ptr) + Sys_Error ("Z_Realloc: failed on allocation of %i bytes", size); + + if (ptr != old_ptr) + memmove (ptr, old_ptr, q_min(old_size, size)); + if (old_size < size) + memset ((byte *)ptr + old_size, 0, size - old_size); + + return ptr; +} + +char *Z_Strdup (const char *s) +{ + size_t sz = strlen(s) + 1; + char *ptr = (char *) Z_Malloc (sz); + memcpy (ptr, s, sz); + return ptr; +} + + +/* +======================== +Z_Print +======================== +*/ +void Z_Print (memzone_t *zone) +{ + memblock_t *block; + + Con_Printf ("zone size: %i location: %p\n",mainzone->size,mainzone); + + for (block = zone->blocklist.next ; ; block = block->next) + { + Con_Printf ("block:%p size:%7i tag:%3i\n", + block, block->size, block->tag); + + if (block->next == &zone->blocklist) + break; // all blocks have been hit + if ( (byte *)block + block->size != (byte *)block->next) + Con_Printf ("ERROR: block size does not touch the next block\n"); + if ( block->next->prev != block) + Con_Printf ("ERROR: next block doesn't have proper back link\n"); + if (!block->tag && !block->next->tag) + Con_Printf ("ERROR: two consecutive free blocks\n"); + } +} + + +//============================================================================ + +#define HUNK_SENTINAL 0x1df001ed + +#define HUNKNAME_LEN 24 +typedef struct +{ + int sentinal; + int size; // including sizeof(hunk_t), -1 = not allocated + char name[HUNKNAME_LEN]; +} hunk_t; + +byte *hunk_base; +int hunk_size; + +int hunk_low_used; +int hunk_high_used; + +qboolean hunk_tempactive; +int hunk_tempmark; + +/* +============== +Hunk_Check + +Run consistancy and sentinal trahing checks +============== +*/ +void Hunk_Check (void) +{ + hunk_t *h; + + for (h = (hunk_t *)hunk_base ; (byte *)h != hunk_base + hunk_low_used ; ) + { + if (h->sentinal != HUNK_SENTINAL) + Sys_Error ("Hunk_Check: trahsed sentinal"); + if (h->size < (int) sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size) + Sys_Error ("Hunk_Check: bad size"); + h = (hunk_t *)((byte *)h+h->size); + } +} + +/* +============== +Hunk_Print + +If "all" is specified, every single allocation is printed. +Otherwise, allocations with the same name will be totaled up before printing. +============== +*/ +void Hunk_Print (qboolean all) +{ + hunk_t *h, *next, *endlow, *starthigh, *endhigh; + int count, sum; + int totalblocks; + char name[HUNKNAME_LEN]; + + count = 0; + sum = 0; + totalblocks = 0; + + h = (hunk_t *)hunk_base; + endlow = (hunk_t *)(hunk_base + hunk_low_used); + starthigh = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); + endhigh = (hunk_t *)(hunk_base + hunk_size); + + Con_Printf (" :%8i total hunk size\n", hunk_size); + Con_Printf ("-------------------------\n"); + + while (1) + { + // + // skip to the high hunk if done with low hunk + // + if ( h == endlow ) + { + Con_Printf ("-------------------------\n"); + Con_Printf (" :%8i REMAINING\n", hunk_size - hunk_low_used - hunk_high_used); + Con_Printf ("-------------------------\n"); + h = starthigh; + } + + // + // if totally done, break + // + if ( h == endhigh ) + break; + + // + // run consistancy checks + // + if (h->sentinal != HUNK_SENTINAL) + Sys_Error ("Hunk_Check: trahsed sentinal"); + if (h->size < (int) sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size) + Sys_Error ("Hunk_Check: bad size"); + + next = (hunk_t *)((byte *)h+h->size); + count++; + totalblocks++; + sum += h->size; + + // + // print the single block + // + memcpy (name, h->name, HUNKNAME_LEN); + if (all) + Con_Printf ("%8p :%8i %8s\n",h, h->size, name); + + // + // print the total + // + if (next == endlow || next == endhigh || + strncmp (h->name, next->name, HUNKNAME_LEN - 1)) + { + if (!all) + Con_Printf (" :%8i %8s (TOTAL)\n",sum, name); + count = 0; + sum = 0; + } + + h = next; + } + + Con_Printf ("-------------------------\n"); + Con_Printf ("%8i total blocks\n", totalblocks); + +} + +/* +=================== +Hunk_Print_f -- johnfitz -- console command to call hunk_print +=================== +*/ +void Hunk_Print_f (void) +{ + Hunk_Print (false); +} + +/* +=================== +Hunk_AllocName +=================== +*/ +void *Hunk_AllocName (int size, const char *name) +{ + hunk_t *h; + +#ifdef PARANOID + Hunk_Check (); +#endif + + if (size < 0) + Sys_Error ("Hunk_Alloc: bad size: %i", size); + + size = sizeof(hunk_t) + ((size+15)&~15); + + if (hunk_size - hunk_low_used - hunk_high_used < size) + Sys_Error ("Hunk_Alloc: failed on %i bytes",size); + + h = (hunk_t *)(hunk_base + hunk_low_used); + hunk_low_used += size; + + Cache_FreeLow (hunk_low_used); + + memset (h, 0, size); + + h->size = size; + h->sentinal = HUNK_SENTINAL; + q_strlcpy (h->name, name, HUNKNAME_LEN); + + return (void *)(h+1); +} + +/* +=================== +Hunk_Alloc +=================== +*/ +void *Hunk_Alloc (int size) +{ + return Hunk_AllocName (size, "unknown"); +} + +int Hunk_LowMark (void) +{ + return hunk_low_used; +} + +void Hunk_FreeToLowMark (int mark) +{ + if (mark < 0 || mark > hunk_low_used) + Sys_Error ("Hunk_FreeToLowMark: bad mark %i", mark); + memset (hunk_base + mark, 0, hunk_low_used - mark); + hunk_low_used = mark; +} + +int Hunk_HighMark (void) +{ + if (hunk_tempactive) + { + hunk_tempactive = false; + Hunk_FreeToHighMark (hunk_tempmark); + } + + return hunk_high_used; +} + +void Hunk_FreeToHighMark (int mark) +{ + if (hunk_tempactive) + { + hunk_tempactive = false; + Hunk_FreeToHighMark (hunk_tempmark); + } + if (mark < 0 || mark > hunk_high_used) + Sys_Error ("Hunk_FreeToHighMark: bad mark %i", mark); + memset (hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark); + hunk_high_used = mark; +} + + +/* +=================== +Hunk_HighAllocName +=================== +*/ +void *Hunk_HighAllocName (int size, const char *name) +{ + hunk_t *h; + + if (size < 0) + Sys_Error ("Hunk_HighAllocName: bad size: %i", size); + + if (hunk_tempactive) + { + Hunk_FreeToHighMark (hunk_tempmark); + hunk_tempactive = false; + } + +#ifdef PARANOID + Hunk_Check (); +#endif + + size = sizeof(hunk_t) + ((size+15)&~15); + + if (hunk_size - hunk_low_used - hunk_high_used < size) + { + Con_Printf ("Hunk_HighAlloc: failed on %i bytes\n",size); + return NULL; + } + + hunk_high_used += size; + Cache_FreeHigh (hunk_high_used); + + h = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); + + memset (h, 0, size); + h->size = size; + h->sentinal = HUNK_SENTINAL; + q_strlcpy (h->name, name, HUNKNAME_LEN); + + return (void *)(h+1); +} + + +/* +================= +Hunk_TempAlloc + +Return space from the top of the hunk +================= +*/ +void *Hunk_TempAlloc (int size) +{ + void *buf; + + size = (size+15)&~15; + + if (hunk_tempactive) + { + Hunk_FreeToHighMark (hunk_tempmark); + hunk_tempactive = false; + } + + hunk_tempmark = Hunk_HighMark (); + + buf = Hunk_HighAllocName (size, "temp"); + + hunk_tempactive = true; + + return buf; +} + +char *Hunk_Strdup (const char *s, const char *name) +{ + size_t sz = strlen(s) + 1; + char *ptr = (char *) Hunk_AllocName (sz, name); + memcpy (ptr, s, sz); + return ptr; +} + +/* +=============================================================================== + +CACHE MEMORY + +=============================================================================== +*/ + +#define CACHENAME_LEN 32 +typedef struct cache_system_s +{ + int size; // including this header + cache_user_t *user; + char name[CACHENAME_LEN]; + struct cache_system_s *prev, *next; + struct cache_system_s *lru_prev, *lru_next; // for LRU flushing +} cache_system_t; + +cache_system_t *Cache_TryAlloc (int size, qboolean nobottom); + +cache_system_t cache_head; + +/* +=========== +Cache_Move +=========== +*/ +void Cache_Move ( cache_system_t *c) +{ + cache_system_t *new_cs; + +// we are clearing up space at the bottom, so only allocate it late + new_cs = Cache_TryAlloc (c->size, true); + if (new_cs) + { +// Con_Printf ("cache_move ok\n"); + + Q_memcpy ( new_cs+1, c+1, c->size - sizeof(cache_system_t) ); + new_cs->user = c->user; + Q_memcpy (new_cs->name, c->name, sizeof(new_cs->name)); + Cache_Free (c->user, false); //johnfitz -- added second argument + new_cs->user->data = (void *)(new_cs+1); + } + else + { +// Con_Printf ("cache_move failed\n"); + + Cache_Free (c->user, true); // tough luck... //johnfitz -- added second argument + } +} + +/* +============ +Cache_FreeLow + +Throw things out until the hunk can be expanded to the given point +============ +*/ +void Cache_FreeLow (int new_low_hunk) +{ + cache_system_t *c; + + while (1) + { + c = cache_head.next; + if (c == &cache_head) + return; // nothing in cache at all + if ((byte *)c >= hunk_base + new_low_hunk) + return; // there is space to grow the hunk + Cache_Move ( c ); // reclaim the space + } +} + +/* +============ +Cache_FreeHigh + +Throw things out until the hunk can be expanded to the given point +============ +*/ +void Cache_FreeHigh (int new_high_hunk) +{ + cache_system_t *c, *prev; + + prev = NULL; + while (1) + { + c = cache_head.prev; + if (c == &cache_head) + return; // nothing in cache at all + if ( (byte *)c + c->size <= hunk_base + hunk_size - new_high_hunk) + return; // there is space to grow the hunk + if (c == prev) + Cache_Free (c->user, true); // didn't move out of the way //johnfitz -- added second argument + else + { + Cache_Move (c); // try to move it + prev = c; + } + } +} + +void Cache_UnlinkLRU (cache_system_t *cs) +{ + if (!cs->lru_next || !cs->lru_prev) + Sys_Error ("Cache_UnlinkLRU: NULL link"); + + cs->lru_next->lru_prev = cs->lru_prev; + cs->lru_prev->lru_next = cs->lru_next; + + cs->lru_prev = cs->lru_next = NULL; +} + +void Cache_MakeLRU (cache_system_t *cs) +{ + if (cs->lru_next || cs->lru_prev) + Sys_Error ("Cache_MakeLRU: active link"); + + cache_head.lru_next->lru_prev = cs; + cs->lru_next = cache_head.lru_next; + cs->lru_prev = &cache_head; + cache_head.lru_next = cs; +} + +/* +============ +Cache_TryAlloc + +Looks for a free block of memory between the high and low hunk marks +Size should already include the header and padding +============ +*/ +cache_system_t *Cache_TryAlloc (int size, qboolean nobottom) +{ + cache_system_t *cs, *new_cs; + +// is the cache completely empty? + + if (!nobottom && cache_head.prev == &cache_head) + { + if (hunk_size - hunk_high_used - hunk_low_used < size) + Sys_Error ("Cache_TryAlloc: %i is greater then free hunk", size); + + new_cs = (cache_system_t *) (hunk_base + hunk_low_used); + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + cache_head.prev = cache_head.next = new_cs; + new_cs->prev = new_cs->next = &cache_head; + + Cache_MakeLRU (new_cs); + return new_cs; + } + +// search from the bottom up for space + + new_cs = (cache_system_t *) (hunk_base + hunk_low_used); + cs = cache_head.next; + + do + { + if (!nobottom || cs != cache_head.next) + { + if ( (byte *)cs - (byte *)new_cs >= size) + { // found space + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + new_cs->next = cs; + new_cs->prev = cs->prev; + cs->prev->next = new_cs; + cs->prev = new_cs; + + Cache_MakeLRU (new_cs); + + return new_cs; + } + } + + // continue looking + new_cs = (cache_system_t *)((byte *)cs + cs->size); + cs = cs->next; + + } while (cs != &cache_head); + +// try to allocate one at the very end + if ( hunk_base + hunk_size - hunk_high_used - (byte *)new_cs >= size) + { + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + new_cs->next = &cache_head; + new_cs->prev = cache_head.prev; + cache_head.prev->next = new_cs; + cache_head.prev = new_cs; + + Cache_MakeLRU (new_cs); + + return new_cs; + } + + return NULL; // couldn't allocate +} + +/* +============ +Cache_Flush + +Throw everything out, so new data will be demand cached +============ +*/ +void Cache_Flush (void) +{ + while (cache_head.next != &cache_head) + Cache_Free ( cache_head.next->user, true); // reclaim the space //johnfitz -- added second argument +} + +/* +============ +Cache_Print + +============ +*/ +void Cache_Print (void) +{ + cache_system_t *cd; + + for (cd = cache_head.next ; cd != &cache_head ; cd = cd->next) + { + Con_Printf ("%8i : %s\n", cd->size, cd->name); + } +} + +/* +============ +Cache_Report + +============ +*/ +void Cache_Report (void) +{ + Con_DPrintf ("%4.1f megabyte data cache\n", (hunk_size - hunk_high_used - hunk_low_used) / (float)(1024*1024) ); +} + +/* +============ +Cache_Init + +============ +*/ +void Cache_Init (void) +{ + cache_head.next = cache_head.prev = &cache_head; + cache_head.lru_next = cache_head.lru_prev = &cache_head; + + Cmd_AddCommand ("flush", Cache_Flush); +} + +/* +============== +Cache_Free + +Frees the memory and removes it from the LRU list +============== +*/ +void Cache_Free (cache_user_t *c, qboolean freetextures) //johnfitz -- added second argument +{ + cache_system_t *cs; + + if (!c->data) + Sys_Error ("Cache_Free: not allocated"); + + cs = ((cache_system_t *)c->data) - 1; + + cs->prev->next = cs->next; + cs->next->prev = cs->prev; + cs->next = cs->prev = NULL; + + c->data = NULL; + + Cache_UnlinkLRU (cs); + + //johnfitz -- if a model becomes uncached, free the gltextures. This only works + //becuase the cache_user_t is the last component of the qmodel_t struct. Should + //fail harmlessly if *c is actually part of an sfx_t struct. I FEEL DIRTY + if (freetextures) + TexMgr_FreeTexturesForOwner ((qmodel_t *)(c + 1) - 1); +} + + + +/* +============== +Cache_Check +============== +*/ +void *Cache_Check (cache_user_t *c) +{ + cache_system_t *cs; + + if (!c->data) + return NULL; + + cs = ((cache_system_t *)c->data) - 1; + +// move to head of LRU + Cache_UnlinkLRU (cs); + Cache_MakeLRU (cs); + + return c->data; +} + + +/* +============== +Cache_Alloc +============== +*/ +void *Cache_Alloc (cache_user_t *c, int size, const char *name) +{ + cache_system_t *cs; + + if (c->data) + Sys_Error ("Cache_Alloc: allready allocated"); + + if (size <= 0) + Sys_Error ("Cache_Alloc: size %i", size); + + size = (size + sizeof(cache_system_t) + 15) & ~15; + +// find memory for it + while (1) + { + cs = Cache_TryAlloc (size, false); + if (cs) + { + q_strlcpy (cs->name, name, CACHENAME_LEN); + c->data = (void *)(cs+1); + cs->user = c; + break; + } + + // free the least recently used cahedat + if (cache_head.lru_prev == &cache_head) + Sys_Error ("Cache_Alloc: out of memory"); // not enough memory at all + + Cache_Free (cache_head.lru_prev->user, true); //johnfitz -- added second argument + } + + return Cache_Check (c); +} + +//============================================================================ + + +static void Memory_InitZone (memzone_t *zone, int size) +{ + memblock_t *block; + +// set the entire zone to one free block + + zone->blocklist.next = zone->blocklist.prev = block = + (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + zone->blocklist.tag = 1; // in use block + zone->blocklist.id = 0; + zone->blocklist.size = 0; + zone->rover = block; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof(memzone_t); +} + +/* +======================== +Memory_Init +======================== +*/ +void Memory_Init (void *buf, int size) +{ + int p; + int zonesize = DYNAMIC_SIZE; + + hunk_base = (byte *) buf; + hunk_size = size; + hunk_low_used = 0; + hunk_high_used = 0; + + Cache_Init (); + p = COM_CheckParm ("-zone"); + if (p) + { + if (p < com_argc-1) + zonesize = Q_atoi (com_argv[p+1]) * 1024; + else + Sys_Error ("Memory_Init: you must specify a size in KB after -zone"); + } + mainzone = (memzone_t *) Hunk_AllocName (zonesize, "zone" ); + Memory_InitZone (mainzone, zonesize); + + Cmd_AddCommand ("hunk_print", Hunk_Print_f); //johnfitz +} + diff --git a/source/zone.h b/source/zone.h new file mode 100644 index 0000000..393e322 --- /dev/null +++ b/source/zone.h @@ -0,0 +1,134 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +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 __ZZONE_H +#define __ZZONE_H + +/* + memory allocation + + +H_??? The hunk manages the entire memory block given to quake. It must be +contiguous. Memory can be allocated from either the low or high end in a +stack fashion. The only way memory is released is by resetting one of the +pointers. + +Hunk allocations should be given a name, so the Hunk_Print () function +can display usage. + +Hunk allocations are guaranteed to be 16 byte aligned. + +The video buffers are allocated high to avoid leaving a hole underneath +server allocations when changing to a higher video mode. + + +Z_??? Zone memory functions used for small, dynamic allocations like text +strings from command input. There is only about 48K for it, allocated at +the very bottom of the hunk. + +Cache_??? Cache memory is for objects that can be dynamically loaded and +can usefully stay persistant between levels. The size of the cache +fluctuates from level to level. + +To allocate a cachable object + + +Temp_??? Temp memory is used for file loading and surface caching. The size +of the cache memory is adjusted so that there is a minimum of 512k remaining +for temp memory. + + +------ Top of Memory ------- + +high hunk allocations + +<--- high hunk reset point held by vid + +video buffer + +z buffer + +surface cache + +<--- high hunk used + +cachable memory + +<--- low hunk used + +client and server low hunk allocations + +<-- low hunk reset point held by host + +startup hunk allocations + +Zone block + +----- Bottom of Memory ----- + + + +*/ + +void Memory_Init (void *buf, int size); + +void Z_Free (void *ptr); +void *Z_Malloc (int size); // returns 0 filled memory +void *Z_Realloc (void *ptr, int size); +char *Z_Strdup (const char *s); + +void *Hunk_Alloc (int size); // returns 0 filled memory +void *Hunk_AllocName (int size, const char *name); +void *Hunk_HighAllocName (int size, const char *name); +char *Hunk_Strdup (const char *s, const char *name); + +int Hunk_LowMark (void); +void Hunk_FreeToLowMark (int mark); + +int Hunk_HighMark (void); +void Hunk_FreeToHighMark (int mark); + +void *Hunk_TempAlloc (int size); + +void Hunk_Check (void); + +typedef struct cache_user_s +{ + void *data; +} cache_user_t; + +void Cache_Flush (void); + +void *Cache_Check (cache_user_t *c); +// returns the cached data, and moves to the head of the LRU list +// if present, otherwise returns NULL + +void Cache_Free (cache_user_t *c, qboolean freetextures); //johnfitz -- added second argument + +void *Cache_Alloc (cache_user_t *c, int size, const char *name); +// Returns NULL if all purgable data was tossed and there still +// wasn't enough room. + +void Cache_Report (void); + +#endif /* __ZZONE_H */ +